this.map = map;
init();
way.addEventListener(Connection.TAG_CHANGE, wayTagChanged);
+ way.addEventListener(Connection.WAY_NODE_ADDED, wayNodeAdded);
+ way.addEventListener(Connection.WAY_NODE_REMOVED, wayNodeRemoved);
+ attachNodeListeners();
+ }
+
+ private function attachNodeListeners():void {
for (var i:uint = 0; i < way.length; i++ ) {
way.getNode(i).addEventListener(Connection.NODE_MOVED, nodeMoved);
}
}
+ private function wayNodeAdded(event:WayNodeEvent):void {
+ event.node.addEventListener(Connection.NODE_MOVED, nodeMoved);
+ redraw();
+ }
+
+ private function wayNodeRemoved(event:WayNodeEvent):void {
+ event.node.removeEventListener(Connection.NODE_MOVED, nodeMoved);
+ redraw();
+ }
+
private function wayTagChanged(event:TagEvent):void {
redraw();
}
var node:Node = way.getNode(i);
var nodeX:Number = map.lon2coord(node.lon);
var nodeY:Number = map.latp2coord(node.latp);
- if ( nodeX >= x-2 && nodeX <= x+2 &&
- nodeY >= y-2 && nodeY <= y+2 )
+ if ( nodeX >= x-3 && nodeX <= x+3 &&
+ nodeY >= y-3 && nodeY <= y+3 )
return node;
}
return null;
public static var NEW_POI:String = "new_poi";
public static var TAG_CHANGE:String = "tag_change";
public static var NODE_MOVED:String = "node_moved";
+ public static var WAY_NODE_ADDED:String = "way_node_added";
+ public static var WAY_NODE_REMOVED:String = "way_node_removed";
// store the data we download
private var negativeID:Number = -1;
if (relation.loaded) { sendEvent(new EntityEvent(NEW_RELATION, relation)); }
}
+ protected function renumberNode(oldID:Number, node:Node):void {
+ nodes[node.id] = node;
+ delete nodes[oldID];
+ }
+
+ protected function renumberWay(oldID:Number, way:Way):void {
+ ways[way.id] = way;
+ delete ways[oldID];
+ }
+
+ protected function renumberRelation(oldID:Number, relation:Relation):void {
+ relations[relation.id] = relation;
+ delete relations[oldID];
+ }
+
public function sendEvent(e:*):void {
dispatchEvent(e);
}
return _loaded;
}
- public function updateEntityProperties(v:uint,t:Object,l:Boolean):void {
- _version=v; tags=t; _loaded=l;
+ public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean):void {
+ _version=version; this.tags=tags; _loaded=loaded;
}
// Tag-handling methods
for (var o:Object in parents) { a.push(o); }
return a;
}
-
+
+ public function hasParent(entity:Entity):Boolean {
+ return parents[entity] == true;
+ }
+
// To be overridden
public function getType():String {
public function Node(id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number) {
super(id, version, tags, loaded);
- this.lat = lat;
- this.lon = lon;
+ this._lat = lat;
+ this._latproj = lat2latp(lat);
+ this._lon = lon;
}
public function update(version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number):void {
}
public function set latp(latproj:Number):void {
+ var oldLat:Number = this._lat;
this._latproj = latproj;
this._lat = latp2lat(lat);
- }
+ markDirty();
+ dispatchEvent(new NodeMovedEvent(Connection.NODE_MOVED, this, oldLat, _lon));
+ }
public function set lon(lon:Number):void {
+ var oldLon:Number = this._lon;
this._lon = lon;
- }
+ markDirty();
+ dispatchEvent(new NodeMovedEvent(Connection.NODE_MOVED, this, _lat, oldLon));
+ }
public override function toString():String {
return "Node("+id+"@"+version+"): "+lat+","+lon+" "+getTagList();
package net.systemeD.halcyon.connection {
+ import flash.geom.Point;
public class Way extends Entity {
private var nodes:Array;
public function insertNode(index:uint, node:Node):void {
node.addParent(this);
nodes.splice(index, 0, node);
+ markDirty();
+ dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, index));
}
public function appendNode(node:Node):uint {
node.addParent(this);
nodes.push(node);
+ markDirty();
+ dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, this, nodes.length - 1));
return nodes.length;
}
public function removeNode(index:uint):void {
var removed:Array=nodes.splice(index, 1);
removed[0].removeParent(this);
+ markDirty();
+ dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], this, index));
}
+ /**
+ * Finds the 1st way segment which intersects the projected
+ * coordinate and adds the node to that segment. If snap is
+ * specified then the node is moved to exactly bisect the
+ * segment.
+ */
+ public function insertNodeAtClosestPosition(newNode:Node, isSnap:Boolean):int {
+ var closestProportion:Number = 1;
+ var newIndex:uint = 0;
+ var nP:Point = new Point(newNode.lon, newNode.latp);
+ var snapped:Point = null;
+
+ for ( var i:uint; i < length - 1; i++ ) {
+ var node1:Node = getNode(i);
+ var node2:Node = getNode(i+1);
+ var p1:Point = new Point(node1.lon, node1.latp);
+ var p2:Point = new Point(node2.lon, node2.latp);
+
+ var directDist:Number = Point.distance(p1, p2);
+ var viaNewDist:Number = Point.distance(p1, nP) + Point.distance(nP, p2);
+
+ var proportion:Number = Math.abs(viaNewDist/directDist - 1);
+ if ( proportion < closestProportion ) {
+ newIndex = i+1;
+ closestProportion = proportion;
+ snapped = calculateSnappedPoint(p1, p2, nP);
+ }
+ }
+
+ // splice in new node
+ if ( isSnap ) {
+ newNode.latp = snapped.y;
+ newNode.lon = snapped.x;
+ }
+ insertNode(newIndex, newNode);
+ return newIndex;
+ }
+
+ private function calculateSnappedPoint(p1:Point, p2:Point, nP:Point):Point {
+ var w:Number = p2.x - p1.x;
+ var h:Number = p2.y - p1.y;
+ var u:Number = ((nP.x-p1.x) * w + (nP.y-p1.y) * h) / (w*w + h*h);
+ return new Point(p1.x + u*w, p1.y+u*h);
+ }
+
public override function toString():String {
return "Way("+id+"@"+version+"): "+getTagList()+
" "+nodes.map(function(item:Node,index:int, arr:Array):String {return item.id.toString();}).join(",");
--- /dev/null
+package net.systemeD.halcyon.connection {
+
+ import flash.events.Event;
+
+ public class WayNodeEvent extends EntityEvent {
+ private var _way:Way;
+ private var _index:uint;
+
+ public function WayNodeEvent(type:String, node:Node, way:Way, index:uint) {
+ super(type, node);
+ this._way = way;
+ this._index = index;
+ }
+
+ public function get way():Way { return _way; }
+ public function get node():Node { return entity as Node; }
+ public function get index():uint { return _index; }
+
+ public override function toString():String {
+ return super.toString() + " in "+_way+" at "+_index;
+ }
+ }
+
+}
+
upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
-
+
// *** TODO *** deleting items
// now actually upload them
else if ( type == "relation" ) entity = getRelation(oldID);
entity.markClean(newID, version);
- // *** TODO *** handle renumbering of creates, and deleting
+ if ( oldID != newID ) {
+ if ( type == "node" ) renumberNode(oldID, entity as Node);
+ else if ( type == "way" ) renumberWay(oldID, entity as Way);
+ else if ( type == "relation" ) renumberRelation(oldID, entity as Relation);
+ }
+ // *** TODO *** handle deleting
}
dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
import net.systemeD.halcyon.Map;
import net.systemeD.halcyon.MapController;
import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.controller.*;
import flash.events.*;
import flash.geom.*;
public class EditController implements MapController {
- private var map:Map;
+ private var _map:Map;
private var tagViewer:TagViewer;
- private var selectedEntity:Entity;
+ private var selectedWay:Way;
+ private var selectedNode:Node;
private var draggingNode:Node = null;
+ private var state:ControllerState;
+ private var _connection:Connection;
+
public function EditController(map:Map, tagViewer:TagViewer) {
- this.map = map;
+ this._map = map;
this.tagViewer = tagViewer;
+ setState(new NoSelection());
map.parent.addEventListener(MouseEvent.MOUSE_MOVE, mapMouseEvent);
map.parent.addEventListener(MouseEvent.MOUSE_UP, mapMouseEvent);
map.parent.addEventListener(MouseEvent.MOUSE_DOWN, mapMouseEvent);
+ map.parent.addEventListener(MouseEvent.CLICK, mapMouseEvent);
}
public function setActive():void {
map.setController(this);
+ _connection = map.connection;
}
+ public function get map():Map {
+ return _map;
+ }
+
+ public function get connection():Connection {
+ return _connection;
+ }
+
+ public function setTagViewer(entity:Entity):void {
+ tagViewer.setEntity(entity);
+ }
+
private function mapMouseEvent(event:MouseEvent):void {
- if ( draggingNode != null ) {
- var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
- event.localX = mapLoc.x;
- event.localY = mapLoc.y;
+ var mapLoc:Point = map.globalToLocal(new Point(event.stageX, event.stageY));
+ event.localX = mapLoc.x;
+ event.localY = mapLoc.y;
- processNodeEvent(event, null);
+ var newState:ControllerState = state.processMouseEvent(event, null);
+ setState(newState);
+ if ( draggingNode != null ) {
}
}
public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
- if ( event.type == MouseEvent.MOUSE_DOWN )
+ //if ( event.type == MouseEvent.MOUSE_DOWN )
event.stopPropagation();
+
+ var newState:ControllerState = state.processMouseEvent(event, entity);
+ setState(newState);
+ /*
if ( entity is Node || draggingNode != null ) {
processNodeEvent(event, entity);
- return;
+ } else if ( enity is Way ) {
+ processWayEvent(event, entity);
}
if ( event.type == MouseEvent.CLICK ) {
- if ( selectedEntity != null ) {
- map.setHighlight(selectedEntity, "selected", false);
- map.setHighlight(selectedEntity, "showNodes", false);
+ if ( selectedWay != null ) {
+ map.setHighlight(selectedWay, "selected", false);
+ map.setHighlight(selectedWay, "showNodes", false);
}
tagViewer.setEntity(entity);
map.setHighlight(entity, "selected", true);
map.setHighlight(entity, "showNodes", true);
- selectedEntity = entity;
+ selectedWay = entity;
} else if ( event.type == MouseEvent.MOUSE_OVER )
map.setHighlight(entity, "hover", true);
else if ( event.type == MouseEvent.MOUSE_OUT )
map.setHighlight(entity, "hover", false);
-
+ */
+ }
+
+ private function setState(newState:ControllerState):void {
+ if ( newState == state )
+ return;
+
+ if ( state != null )
+ state.exitState();
+ newState.setController(this);
+ newState.setPreviousState(state);
+ state = newState;
+ state.enterState();
}
private function processNodeEvent(event:MouseEvent, entity:Entity):void {
}
+
}
if ( selectedEntity != null )
selectedEntity.removeEventListener(Connection.TAG_CHANGE, tagChanged);
selectedEntity = entity;
- selectedEntity.addEventListener(Connection.TAG_CHANGE, tagChanged);
+ if ( selectedEntity != null )
+ selectedEntity.addEventListener(Connection.TAG_CHANGE, tagChanged);
}
if ( advancedID != null )
}
private function refreshFeatureIcon():void {
- feature = mapFeatures.findMatchingFeature(selectedEntity);
+ feature = selectedEntity == null ? null : mapFeatures.findMatchingFeature(selectedEntity);
if ( feature != null )
setFeatureIcon(selectedEntity, feature);
else
}
private function setupAdvanced(entity:Entity):void {
- var entityText:String = "xx";
- if ( entity is Node ) entityText = "Node";
- else if ( entity is Way ) entityText = "Way";
- else if ( entity is Relation ) entityText = "Relation";
-
- advancedID.htmlText = entityText+": <b>"+entity.id+"</b>";
-
if ( collection == null ) {
collection = new ArrayCollection();
- //var sort:Sort = new Sort();
- //sort.fields = [new SortField("key", true)];
- //collection.sort = sort;
- //collection.refresh();
advancedTagGrid.dataProvider = collection;
}
+
collection.removeAll();
- var tags:Array = entity.getTagArray();
- tags.sortOn("key");
- for each(var tag:Tag in tags)
- collection.addItem(tag);
+
+ if ( entity == null ) {
+ advancedID.htmlText = "";
+ } else {
+ var entityText:String = "xx";
+ if ( entity is Node ) entityText = "Node";
+ else if ( entity is Way ) entityText = "Way";
+ else if ( entity is Relation ) entityText = "Relation";
+ advancedID.htmlText = entityText+": <b>"+entity.id+"</b>";
+
+ var tags:Array = entity.getTagArray();
+ tags.sortOn("key");
+ for each(var tag:Tag in tags)
+ collection.addItem(tag);
+ }
}
private function tagChanged(event:TagEvent):void {
--- /dev/null
+package net.systemeD.potlatch2.controller {
+ import flash.events.*;
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.EditController;
+
+ public class ControllerState {
+
+ protected var controller:EditController;
+ protected var previousState:ControllerState;
+
+ public function ControllerState() {}
+
+ public function setController(controller:EditController):void {
+ this.controller = controller;
+ }
+
+ public function setPreviousState(previousState:ControllerState):void {
+ if ( this.previousState == null )
+ this.previousState = previousState;
+ }
+
+ public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+ return this;
+ }
+
+ public function enterState():void {}
+ public function exitState():void {}
+
+ }
+}
--- /dev/null
+package net.systemeD.potlatch2.controller {
+ import flash.events.*;
+ import net.systemeD.potlatch2.EditController;
+ import net.systemeD.halcyon.connection.*;
+
+ public class DragWayNode extends ControllerState {
+ private var selectedWay:Way;
+ private var draggingNode:Node;
+ private var isDraggingStarted:Boolean = false;
+ private var downX:Number;
+ private var downY:Number;
+
+ public function DragWayNode(way:Way, node:Node, mouseDown:MouseEvent) {
+ selectedWay = way;
+ draggingNode = node;
+ downX = mouseDown.localX;
+ downY = mouseDown.localY;
+ }
+
+ override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+ if ( event.type == MouseEvent.MOUSE_UP )
+ return endDrag();
+
+ if ( !isDragging(event) )
+ return this;
+
+ if ( event.type == MouseEvent.MOUSE_MOVE )
+ return dragTo(event);
+ else
+ return this;
+ }
+
+ private function isDragging(event:MouseEvent):Boolean {
+ if ( isDraggingStarted )
+ return true;
+
+ isDraggingStarted = Math.abs(downX - event.localX) > 3 ||
+ Math.abs(downY - event.localY) > 3;
+ return isDraggingStarted;
+ }
+
+ private function endDrag():ControllerState {
+ return previousState;
+ }
+
+ private function dragTo(event:MouseEvent):ControllerState {
+ draggingNode.lat = controller.map.coord2lat(event.localY);
+ draggingNode.lon = controller.map.coord2lon(event.localX);
+ return this;
+ }
+
+ override public function enterState():void {
+ controller.map.setHighlight(selectedWay, "showNodes", true);
+ }
+ override public function exitState():void {
+ controller.map.setHighlight(selectedWay, "showNodes", false);
+ }
+ }
+}
--- /dev/null
+package net.systemeD.potlatch2.controller {
+ import flash.events.*;
+ import net.systemeD.potlatch2.EditController;
+ import net.systemeD.halcyon.connection.*;
+
+ public class NoSelection extends ControllerState {
+ public function NoSelection() {
+ }
+
+ override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+ var focus:Entity = getTopLevelFocusEntity(entity);
+ if ( event.type == MouseEvent.CLICK )
+ if ( focus is Way )
+ return new SelectedWay(focus as Way);
+ else if ( focus is Node )
+ trace("select poi");
+ else if ( event.type == MouseEvent.MOUSE_OVER )
+ controller.map.setHighlight(focus, "hover", true);
+ else if ( event.type == MouseEvent.MOUSE_OUT )
+ controller.map.setHighlight(focus, "hover", false);
+
+ return this;
+ }
+
+ public static function getTopLevelFocusEntity(entity:Entity):Entity {
+ if ( entity is Node ) {
+ for each (var parent:Entity in entity.parentWays) {
+ return parent;
+ }
+ return entity;
+ } else if ( entity is Way ) {
+ return entity;
+ } else {
+ return null;
+ }
+ }
+ }
+}
--- /dev/null
+package net.systemeD.potlatch2.controller {
+ import flash.events.*;
+ import net.systemeD.potlatch2.EditController;
+ import net.systemeD.halcyon.connection.*;
+
+ public class SelectedWay extends ControllerState {
+ private var selectedWay:Way;
+ private var initWay:Way;
+
+ public function SelectedWay(way:Way) {
+ initWay = way;
+ }
+
+ protected function selectWay(way:Way):void {
+ if ( way == selectedWay )
+ return;
+
+ clearSelection();
+ controller.setTagViewer(way);
+ controller.map.setHighlight(way, "selected", true);
+ controller.map.setHighlight(way, "showNodes", true);
+ selectedWay = way;
+ initWay = way;
+ }
+
+ protected function clearSelection():void {
+ if ( selectedWay != null ) {
+ controller.map.setHighlight(selectedWay, "selected", false);
+ controller.map.setHighlight(selectedWay, "showNodes", false);
+ controller.setTagViewer(null);
+ selectedWay = null;
+ }
+ }
+
+ override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState {
+ var focus:Entity = NoSelection.getTopLevelFocusEntity(entity);
+ if ( event.type == MouseEvent.CLICK ) {
+ if ( (entity is Node && entity.hasParent(selectedWay)) || focus == selectedWay )
+ return clickOnWay(event, entity);
+ else if ( focus is Way )
+ selectWay(focus as Way);
+ else if ( focus is Node )
+ trace("select poi");
+ else if ( focus == null )
+ return previousState;
+ } else if ( event.type == MouseEvent.MOUSE_DOWN ) {
+ if ( entity is Node && entity.hasParent(selectedWay) )
+ return new DragWayNode(selectedWay, Node(entity), event);
+ }
+
+ return this;
+ }
+
+ public function clickOnWay(event:MouseEvent, entity:Entity):ControllerState {
+ if ( event.shiftKey ) {
+ if ( entity is Way )
+ addNode(event);
+ else
+ trace("start new way");
+ } else {
+ if ( entity is Node )
+ trace("select way node");
+ }
+
+ return this;
+ }
+
+ public function addNode(event:MouseEvent):void {
+ trace("add node");
+ var lat:Number = controller.map.coord2lat(event.localY);
+ var lon:Number = controller.map.coord2lon(event.localX);
+ var node:Node = controller.connection.createNode({}, lat, lon);
+ selectedWay.insertNodeAtClosestPosition(node, true);
+ }
+
+ override public function enterState():void {
+ selectWay(initWay);
+ }
+ override public function exitState():void {
+ clearSelection();
+ }
+ }
+}