mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
Current working version
This commit is contained in:
53
README.md
53
README.md
@@ -1,2 +1,51 @@
|
||||
iD
|
||||
==
|
||||
iD - JavaScript beginners' editor for OpenStreetMap
|
||||
===================================================
|
||||
|
||||
Basics
|
||||
------
|
||||
* iD is a JavaScript-based OpenStreetMap editor with MapCSS rendering.
|
||||
* iD is written with the Dojo framework.
|
||||
* It's intentionally simple. iD is not a 90% editor. It's not even a 70% editor. It should let you do the most basic tasks while not breaking other people's data. Nothing more.
|
||||
* Same goes for the code. So go easy on the abstraction. :)
|
||||
* Speaking of percentages, it's about 1% complete.
|
||||
* We're initially targeting WebKit-based browsers and Firefox, using SVG. IE and non-SVG can come later!
|
||||
* The licence of iD is WTFPL and contributions to 'trunk' should accord with this. This does of course allow you to dual-license.
|
||||
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
* Unzip and start playing!
|
||||
* All the code is in js/iD.
|
||||
|
||||
|
||||
How it works
|
||||
------------
|
||||
The code works similarly to Potlatch 2, but with a bit less abstraction. So, we have:
|
||||
|
||||
* Connection: stores, fetches and saves data. (iD/Connection.js)
|
||||
* Entity (Node, Way, Relation): the data objects. (iD/Entity.js)
|
||||
* EntityUI (NodeUI, WayUI): the rendered graphic elements. (iD/renderer/...)
|
||||
* Map: the displayed map on which EntityUIs are rendered. (iD/renderer/Map.js)
|
||||
* Controller: the heart of the app, which does its work via...
|
||||
* ControllerState: the current UI mode. ControllerStates decide what to do in response to mouse/keyboard events. (iD/controller/...)
|
||||
* UndoableAction: the code to actually change the data, as fired by ControllerStates. (iD/actions/...)
|
||||
|
||||
The UI is more modal than Potlatch 2. In particular there's a "draw shape" mode (the "Add road or shape" button) and an "edit object" mode. The directory structure of iD/controller reflects this.
|
||||
|
||||
Other relevant code includes the MapCSS parser in styleparser/ and custom widgets in ui/ .
|
||||
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
Most of the interesting code is in the ControllerStates, which live in iD/controller/. Each one corresponds to a UI mode (e.g. "drawing a way"). Its EntityMouseEvent method takes the user's mouse event (e.g. "clicked on a node"), carries out any actions, and returns the new ControllerState (which might just be 'this', i.e. carry on with the current state).
|
||||
|
||||
|
||||
Coding tips
|
||||
-----------
|
||||
Scoping in JavaScript is famously broken: Dojo's lang.hitch method will save your life. Make sure you include dojo/_base/lang (in the 'declare' statement). Then, when you're passing an instance method as a function parameter, use lang.hitch(instance, instance.method) instead, and Dojo will magically set the right scope. You'll see lots of examples of this throughout the code.
|
||||
|
||||
Instance methods and variables _always_ need to be accessed with 'this.'. This is a fairly frequent gotcha if you're coming from another language.
|
||||
|
||||
You'll find various notes and comments in the docs/ folder. Feel free to add to these.
|
||||
|
||||
More help: ping RichardF on IRC (irc.oftc.net, in #osm-dev or #osm), on the OSM mailing lists or at richard@systemeD.net.
|
||||
|
||||
3
css/app.css
Normal file
3
css/app.css
Normal file
@@ -0,0 +1,3 @@
|
||||
/* Additional CSS rules will go here */
|
||||
|
||||
.currentMode { font-weight: bold; }
|
||||
45
docs/coding_standards.txt
Executable file
45
docs/coding_standards.txt
Executable file
@@ -0,0 +1,45 @@
|
||||
Coding standards and advice for iD
|
||||
==================================
|
||||
|
||||
Classes
|
||||
-------
|
||||
All constructors must initialise objects and arrays. It is not enough to say
|
||||
array:[],
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
but rather, you should do
|
||||
array: null, // effectively a placeholder
|
||||
constructor:function() {
|
||||
this.array=[],
|
||||
},
|
||||
|
||||
or bad things will happen. You should still declare the object outside the constructor, but for clarity rather than functionality.
|
||||
|
||||
(This doesn't apply to simple types - numbers, strings, booleans - which you can declare as normal.)
|
||||
|
||||
Function names
|
||||
--------------
|
||||
Anything that creates and calls an Action should be prefixed with do:
|
||||
doSetLatLon(lat,lon)
|
||||
Anything that is called by an Action, to do the actual work, should be prefixed with an underscore:
|
||||
_setLatLon(lat,lon)
|
||||
and commented as such.
|
||||
|
||||
File naming
|
||||
-----------
|
||||
The filename should be the name of the base class. You can add subclasses within that file for clarity. Don't add extra classes that aren't subclasses, unless they're not referenced from elsewhere.
|
||||
|
||||
Class and variable names
|
||||
------------------------
|
||||
You can prefix function arguments with an underscore to make it clear where they've come from.
|
||||
|
||||
Layout
|
||||
------
|
||||
* Hard tabs, indent of 4.
|
||||
* Do not indent the root level of the module. Add an 'End of module' comment instead.
|
||||
|
||||
Useful stuff to know about Dojo
|
||||
-------------------------------
|
||||
* The array and lang modules are full of useful add-ons to basic JavaScript functionality. lang/hitch will save your life with scopes.
|
||||
|
||||
7
docs/controllerstates.txt
Executable file
7
docs/controllerstates.txt
Executable file
@@ -0,0 +1,7 @@
|
||||
Each ControllerState represents a UI state.
|
||||
|
||||
They are grouped into folders:
|
||||
|
||||
- 'edit' is all states within the 'Edit object' mode
|
||||
- 'shape' is all states within the 'Add shape' mode
|
||||
- 'point' is all states within the 'Add point' mode
|
||||
14
docs/events.txt
Executable file
14
docs/events.txt
Executable file
@@ -0,0 +1,14 @@
|
||||
== Entities ==
|
||||
|
||||
Listeners are created in NodeUI and WayUI for each item's hitzone.
|
||||
|
||||
MouseEvents proceed like this:
|
||||
|
||||
* Event triggered on EntityUI, calling EntityUI.entityMouseEvent(event)
|
||||
* This calls Controller.entityMouseEvent(event,entityUI)
|
||||
* This updates the state from ControllerState.processMouseEvent(event,entityUI)
|
||||
|
||||
== Non-entities ==
|
||||
|
||||
* onmousemove in Map.js calls processMove, which calls Controller.entityMouseEvent(event,null)
|
||||
* onclick in Map.js calls clickSurface, which calls Controller.entityMouseEvent(event,null)
|
||||
63
docs/todo.txt
Executable file
63
docs/todo.txt
Executable file
@@ -0,0 +1,63 @@
|
||||
DrawWay to do:
|
||||
* make junctions
|
||||
* still allow dragging the map
|
||||
|
||||
Drag and drop to do:
|
||||
* icon grid
|
||||
* improve 'avatar'
|
||||
* cope with dragging onto anything, not just blank areas of the map
|
||||
|
||||
Next to do:
|
||||
* drag nodes
|
||||
* remove trailing commas (for IE!)
|
||||
|
||||
Renderer to do
|
||||
* hover!
|
||||
* NodeUI renderer needs a default width/height so that clicking a POI without them still shows the highlight
|
||||
* Fix 'special values' in RuleSet - they're not really special for SVG etc.
|
||||
* Labels other than text-on-path
|
||||
* Node headings (for locks etc.)
|
||||
* Dragging needs tolerance (i.e. less than n pixels and n seconds)
|
||||
|
||||
ControllerStates to do:
|
||||
* Try to share as much code as possible
|
||||
* Draw way controller states:
|
||||
- NoSelection (next click starts, or selects)
|
||||
- SelectedWay (next click starts, or selects)
|
||||
- SelectedPOINode (next click starts, or selects)
|
||||
- DrawWay (next click continues, or completes and edits)
|
||||
|
||||
Events
|
||||
* EntityMouseEvent seems to get called twice most of the time when moving the mouse around in DrawWay
|
||||
* Maybe we should just broadcast to the Controller, and the Controller knows what to do. In other words:
|
||||
- undo says "the following entities have changed: way 5, node 3, way 7, relation 8"
|
||||
- the Controller gets the message and invokes a redraw
|
||||
- no need for anything else to listen
|
||||
|
||||
General code
|
||||
* Do the ***doSetLatLon*** and ***_setLatLon*** naming convention
|
||||
|
||||
------------------------------------------------
|
||||
|
||||
Modes:
|
||||
1. Add point
|
||||
2. Add street or shape
|
||||
3. Edit object
|
||||
3a. edit tags (and relation memberships, etc.)
|
||||
3b. move object
|
||||
3c. delete object
|
||||
3d. extend way
|
||||
3e. geometry operations (like P2 Toolbox)
|
||||
|
||||
In 'edit': double-clicking goes into "draw shape"; double-clicking then Enter creates POI
|
||||
|
||||
Needs greyed-out "step-by-step" window for draw modes:
|
||||
Click at the start point to begin drawing
|
||||
Add each point, click by click
|
||||
Double-click when you've finished
|
||||
Then choose what it is
|
||||
|
||||
With buttons at the bottom:
|
||||
Finish
|
||||
Cancel
|
||||
Undo last
|
||||
11
draganddrop.json
Normal file
11
draganddrop.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
name: "Cafe",
|
||||
icon: "cafe.png",
|
||||
tags: "amenity=cafe"
|
||||
}, {
|
||||
name: "Pub",
|
||||
icon: "pub.png",
|
||||
tags: "amenity=pub"
|
||||
}
|
||||
]
|
||||
BIN
icons/cafe.png
Executable file
BIN
icons/cafe.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 456 B |
BIN
icons/fast_food.png
Executable file
BIN
icons/fast_food.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 598 B |
BIN
icons/parking_cycle.png
Normal file
BIN
icons/parking_cycle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 B |
BIN
icons/pub.png
Normal file
BIN
icons/pub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 B |
BIN
icons/restaurant.png
Executable file
BIN
icons/restaurant.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 590 B |
BIN
icons/school.png
Executable file
BIN
icons/school.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 641 B |
171
index.html
Executable file
171
index.html
Executable file
@@ -0,0 +1,171 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>iD</title>
|
||||
<!-- load Dojo -->
|
||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dijit/themes/claro/claro.css">
|
||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojox/layout/resources/FloatingPane.css">
|
||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojox/layout/resources/ResizeHandle.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/lib/jshashtable.js"></script>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true, baseUrl: 'js/iD/'"></script>
|
||||
<style type="text/css">
|
||||
:focus { outline-color: transparent; outline-style: none; }
|
||||
* { font-family: Helvetica, Arial; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="claro">
|
||||
<div id="appLayout" class="demoLayout">
|
||||
|
||||
<script>
|
||||
|
||||
require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on",
|
||||
"dijit/form/Button","dijit/form/ToggleButton",
|
||||
"dojox/layout/FloatingPane",
|
||||
"iD/actions/UndoStack","iD/actions/CreatePOIAction",
|
||||
"iD/Connection",
|
||||
"iD/Controller",
|
||||
"iD/controller/edit/NoSelection","iD/controller/shape/NoSelection",
|
||||
"iD/renderer/Map","iD/styleparser/RuleSet",
|
||||
"iD/ui/DragAndDrop","iD/ui/StepPane",
|
||||
"dojo/domReady!"], function(lang,domGeom,domClass,on){
|
||||
|
||||
var ruleset=new iD.styleparser.RuleSet();
|
||||
var conn=new iD.Connection("http://www.overpass-api.de/api/xapi?");
|
||||
|
||||
// Load styles
|
||||
ruleset.registerCallback(styleLoaded);
|
||||
ruleset.loadFromCSS("potlatch.css",styleLoaded);
|
||||
|
||||
// Initialise map
|
||||
var map = new iD.renderer.Map(51.87,-1.49,17,"map",conn);
|
||||
conn.registerMap(map);
|
||||
map.ruleset=ruleset;
|
||||
|
||||
// Initialise controller
|
||||
var controller=new iD.Controller(map);
|
||||
map.setController(controller);
|
||||
|
||||
// Initialise event listeners
|
||||
on(window, "enterState", enterStateListener);
|
||||
on(window, "exitState", exitStateListener);
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Data is loaded and app ready to go
|
||||
|
||||
function styleLoaded() {
|
||||
console.log("style loaded");
|
||||
|
||||
// Initialise drag-and-drop icons
|
||||
new iD.ui.DragAndDrop("map",map,"dndgrid");
|
||||
|
||||
// Initialise help pane
|
||||
controller.setStepper(new iD.ui.StepPane("helpPane","helpSteps"));
|
||||
|
||||
// Set initial controllerState
|
||||
controller.setState(new iD.controller.edit.NoSelection());
|
||||
|
||||
// Load data
|
||||
map.download();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// State event listeners
|
||||
|
||||
function enterStateListener(event) {
|
||||
domClass.add(event.state[0]+"Button","currentMode");
|
||||
};
|
||||
|
||||
function exitStateListener(event) {
|
||||
domClass.remove(event.state[0]+"Button","currentMode");
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Mode button handlers
|
||||
|
||||
enterWayMode=function() {
|
||||
console.log('Second button was clicked!');
|
||||
controller.setState(new iD.controller.shape.NoSelection());
|
||||
};
|
||||
|
||||
enterEditMode=function() {
|
||||
console.log('Third button was clicked!');
|
||||
};
|
||||
|
||||
finishClicked=function() {
|
||||
controller.stepper.hide();
|
||||
};
|
||||
|
||||
cancelClicked=function() {
|
||||
controller.stepper.hide();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Map control handlers
|
||||
|
||||
zoomInClicked =function() { map.zoomIn(); };
|
||||
zoomOutClicked=function() { map.zoomOut(); };
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- see http://dojotoolkit.org/documentation/tutorials/1.7/themes_buttons_textboxes/
|
||||
and http://dojotoolkit.org/widgets -->
|
||||
|
||||
<div id="modebuttons" style="position:absolute; left: 10px; top: 10px;">
|
||||
<div id="addPOI" data-dojo-type="dijit.form.DropDownButton" data-dojo-props="iconClass:'dijitIconApplication', onClick:function(){ console.log('First button was clicked!'); }">
|
||||
<span>Add point</span>
|
||||
<div data-dojo-type="dijit.TooltipDialog">
|
||||
<p>Drag points onto the map</p>
|
||||
<table id="dndgrid">
|
||||
</table>
|
||||
</div>
|
||||
</div><script>require(["dijit/form/DropDownButton", "dijit/TooltipDialog","dojo/dnd/Source","dojo/parser", "dojo/domReady!"]);</script>
|
||||
|
||||
<button id="shapeButton" data-dojo-type="dijit.form.ToggleButton" data-dojo-props="onClick:enterWayMode">
|
||||
Add road or shape
|
||||
</button><script>require(["dijit/form/ToggleButton", "dojo/parser", "dojo/domReady!"]);</script>
|
||||
|
||||
<button id="editButton" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:enterEditMode">
|
||||
Edit object
|
||||
</button><script>require(["dijit/form/Button", "dojo/parser", "dojo/domReady!"]);</script>
|
||||
</div>
|
||||
|
||||
<div id="zoombuttons">
|
||||
<button style="position: absolute; left: 10px; top: 40px;"
|
||||
id="zoomIn" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:zoomInClicked">
|
||||
+
|
||||
</button>
|
||||
<button style="position: absolute; left: 10px; top: 70px;"
|
||||
id="zoomOut" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:zoomOutClicked">
|
||||
–
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Map div -->
|
||||
|
||||
<div id="map"
|
||||
style="-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;">
|
||||
</div>
|
||||
|
||||
<!-- Floating help window -->
|
||||
|
||||
<div id="helpPane" data-dojo-type="dojox.layout.FloatingPane"
|
||||
data-dojo-props="resizable:true, closable:false, dockable:false, title: 'Step by step'"
|
||||
style="position:absolute; top:50px; left:10px; width:200px; height:200px; visibility: hidden;" >
|
||||
<ol id="helpSteps">
|
||||
</ol>
|
||||
|
||||
<button id="finishHelpButton" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:finishClicked">
|
||||
Finish
|
||||
</button><script>require(["dijit/form/Button", "dojo/parser", "dojo/domReady!"]);</script>
|
||||
<button id="cancelHelpButton" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:cancelClicked" style="float:right">
|
||||
Cancel
|
||||
</button><script>require(["dijit/form/Button", "dojo/parser", "dojo/domReady!"]);</script>
|
||||
|
||||
</div>
|
||||
|
||||
</div><!-- applayout -->
|
||||
</body>
|
||||
</html>
|
||||
249
js/iD/Connection.js
Executable file
249
js/iD/Connection.js
Executable file
@@ -0,0 +1,249 @@
|
||||
// iD/Connection.js
|
||||
|
||||
define(["dojo/_base/xhr","dojo/_base/lang","dojox/xml/DomParser","dojo/_base/array",'dojo/_base/declare',
|
||||
"iD/Entity","iD/actions/CreateEntityAction"], function(xhr,lang,DomParser,array,declare,Entity){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Connection base class
|
||||
|
||||
declare("iD.Connection", null, {
|
||||
|
||||
nodes: {}, // hash of node objects
|
||||
ways: {}, // hash of way objects
|
||||
relations: {}, // hash of relation objects
|
||||
pois: null, // list of nodes which are POIs
|
||||
maps: [], // list of Map objects listening to this
|
||||
callback: null, // callback once .osm is parsed
|
||||
modified: false, // data has been changed
|
||||
|
||||
nextNode: -1, // next negative ids
|
||||
nextWay: -1, // |
|
||||
nextRelation: -1, // |
|
||||
|
||||
apiBaseURL: '', // root API address
|
||||
|
||||
constructor:function(_apiURL) {
|
||||
console.log("Created a connection");
|
||||
this.nodes={};
|
||||
this.ways={};
|
||||
this.relations={};
|
||||
this.pois=new Hashtable();
|
||||
this.maps=[];
|
||||
this.modified=false;
|
||||
this.apiBaseURL=_apiURL;
|
||||
},
|
||||
|
||||
assign:function(obj) {
|
||||
switch (obj.entityType) {
|
||||
case "node": this.nodes[obj.id]=obj; break;
|
||||
case "way": this.ways[obj.id]=obj; break;
|
||||
case "relation": this.relations[obj.id]=obj; break;
|
||||
}
|
||||
},
|
||||
|
||||
getNode:function(id) { return this.nodes[id]; },
|
||||
getWay:function(id) { return this.ways[id]; },
|
||||
getRelation:function(id) { return this.relations[id]; },
|
||||
|
||||
getOrCreate:function(id,type) {
|
||||
switch (type) {
|
||||
case "node":
|
||||
if (!this.nodes[id]) this.assign(new iD.Node(this, id, NaN, NaN, {}, false));
|
||||
return this.nodes[id];
|
||||
case "way":
|
||||
if (!this.ways[id]) this.assign(new iD.Way(this, id, [], {}, false));
|
||||
return this.ways[id];
|
||||
case "relation":
|
||||
if (!this.relations[id]) this.assign(new iD.Relation(this, id, [], {}, false));
|
||||
return this.relations[id];
|
||||
}
|
||||
},
|
||||
|
||||
createNode:function(tags, lat, lon, perform) {
|
||||
var node = new iD.Node(this, this.nextNode--, lat, lon, tags, true);
|
||||
perform(new iD.actions.CreateEntityAction(node, lang.hitch(this,this.assign) ));
|
||||
return node;
|
||||
},
|
||||
createWay:function(tags, nodes, perform) {
|
||||
var way = new iD.Way(this, this.nextWay--, nodes.concat(), tags, true);
|
||||
perform(new iD.actions.CreateEntityAction(way, lang.hitch(this,this.assign) ));
|
||||
return way;
|
||||
},
|
||||
createRelation:function(tags, members, perform) {
|
||||
var relation = new iD.Relation(this, this.nextRelation--, members.concat(), tags, true);
|
||||
perform(new iD.actions.CreateEntityAction(relation, lang.hitch(this,this.assign) ));
|
||||
return relation;
|
||||
},
|
||||
|
||||
markClean:function() { this.modified=false; },
|
||||
markDirty:function() { this.modified=true; },
|
||||
isDirty:function() { return this.modified; },
|
||||
|
||||
getObjectsByBbox:function(left,right,top,bottom) {
|
||||
var o={ poisInside: [], poisOutside: [],
|
||||
waysInside: [], waysOutside: [] };
|
||||
for (var id in this.ways) {
|
||||
var way=this.ways[id];
|
||||
if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
|
||||
else { o.waysOutside.push(way); }
|
||||
}
|
||||
this.pois.each(function(node,v) {
|
||||
if (node.within(left,right,top,bottom)) { o.poisInside.push(node); }
|
||||
else { o.poisOutside.push(node); }
|
||||
});
|
||||
return o;
|
||||
},
|
||||
|
||||
// Redraw handling
|
||||
|
||||
registerMap:function(map) {
|
||||
this.maps.push(map);
|
||||
},
|
||||
|
||||
refreshMaps:function() {
|
||||
array.forEach(this.maps, function(map) {
|
||||
map.updateUIs(false,true);
|
||||
});
|
||||
},
|
||||
|
||||
refreshEntity:function(_entity) {
|
||||
array.forEach(this.maps, function(map) {
|
||||
map.refreshUI(_entity);
|
||||
});
|
||||
},
|
||||
|
||||
// Callback when completed loading (used in initialisation)
|
||||
|
||||
registerCallback:function(_callback) {
|
||||
this.callback=_callback;
|
||||
},
|
||||
|
||||
// POI handling
|
||||
|
||||
updatePOIs:function(nodelist) {
|
||||
for (var i in nodelist) {
|
||||
if (nodelist[i].hasParentWays()) {
|
||||
this.pois.remove(nodelist[i]);
|
||||
} else {
|
||||
this.pois.put(nodelist[i],true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getPOIs:function() {
|
||||
return this.pois.keys();
|
||||
},
|
||||
|
||||
registerPOI:function(node) {
|
||||
this.pois.put(node,true);
|
||||
},
|
||||
|
||||
unregisterPOI:function(node) {
|
||||
this.pois.remove(node);
|
||||
},
|
||||
|
||||
// OSM parser
|
||||
|
||||
loadFromAPI:function(left,right,top,bottom) {
|
||||
var url="http://www.overpass-api.de/api/xapi?map?bbox="+left+","+bottom+","+right+","+top;
|
||||
xhr.get({ url: url,
|
||||
headers: { "X-Requested-With": null },
|
||||
load: lang.hitch(this, "processOSM") });
|
||||
},
|
||||
|
||||
loadFromURL:function(url) {
|
||||
xhr.get({ url: url, load: lang.hitch(this, "processOSM") });
|
||||
},
|
||||
|
||||
processOSM:function(result) {
|
||||
var jsdom = DomParser.parse(result).childNodes[1];
|
||||
var nodelist = [];
|
||||
for (var i in jsdom.childNodes) {
|
||||
var obj=jsdom.childNodes[i];
|
||||
switch(obj.nodeName) {
|
||||
|
||||
case "node":
|
||||
var node = new iD.Node(this,
|
||||
getAttribute(obj,'id'),
|
||||
getAttribute(obj,'lat'),
|
||||
getAttribute(obj,'lon'),
|
||||
getTags(obj));
|
||||
this.assign(node);
|
||||
nodelist.push(node);
|
||||
break;
|
||||
|
||||
case "way":
|
||||
var way = new iD.Way(this,
|
||||
getAttribute(obj,'id'),
|
||||
getNodes(obj,this),
|
||||
getTags(obj));
|
||||
this.assign(way);
|
||||
break;
|
||||
|
||||
case "relation":
|
||||
var relation = new iD.Relation(this,
|
||||
getAttribute(obj,'id'),
|
||||
getMembers(obj,this),
|
||||
getTags(obj));
|
||||
this.assign(relation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.updatePOIs(nodelist);
|
||||
this.refreshMaps();
|
||||
if (this.callback) { this.callback(); }
|
||||
|
||||
// Private functions to parse DOM created from XML file
|
||||
|
||||
function getAttribute(obj,name) {
|
||||
var result=array.filter(obj.attributes,function(item) {
|
||||
return item.nodeName==name;
|
||||
});
|
||||
return result[0].nodeValue;
|
||||
}
|
||||
|
||||
function getTags(obj) {
|
||||
var tags={};
|
||||
array.forEach(obj.childNodes,function(item) {
|
||||
if (item.nodeName=='tag') {
|
||||
tags[getAttribute(item,'k')]=getAttribute(item,'v');
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
|
||||
function getNodes(obj,conn) {
|
||||
var nodes=[];
|
||||
array.forEach(obj.childNodes,function(item) {
|
||||
if (item.nodeName=='nd') {
|
||||
var id=getAttribute(item,'ref');
|
||||
nodes.push(conn.getNode(id));
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function getMembers(obj,conn) {
|
||||
var members=[];
|
||||
array.forEach(obj.childNodes,function(item) {
|
||||
if (item.nodeName=='member') {
|
||||
var id =getAttribute(item,'ref');
|
||||
var type=getAttribute(item,'type');
|
||||
var role=getAttribute(item,'role');
|
||||
|
||||
var obj=conn.getOrCreate(id,type);
|
||||
members.push(new iD.RelationMember(obj,role));
|
||||
}
|
||||
});
|
||||
return members;
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
45
js/iD/Controller.js
Executable file
45
js/iD/Controller.js
Executable file
@@ -0,0 +1,45 @@
|
||||
// iD/Controller.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/on','iD/actions/UndoStack'], function(declare,on){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Controller base class
|
||||
|
||||
declare("iD.Controller", null, {
|
||||
state: null, // current ControllerState
|
||||
map: null, // current Map
|
||||
stepper: null, // current StepPane
|
||||
undoStack: null, // main undoStack
|
||||
|
||||
constructor:function(_map) {
|
||||
this.map=_map;
|
||||
this.undoStack=new iD.actions.UndoStack();
|
||||
},
|
||||
|
||||
setStepper:function(_stepper) {
|
||||
this.stepper=_stepper;
|
||||
},
|
||||
|
||||
setState:function(newState) {
|
||||
if (newState==this.state) { return; }
|
||||
if (this.state) {
|
||||
this.state.exitState(newState);
|
||||
on.emit(window, "exitState", { bubbles: true, cancelable: true, state: this.state.stateNameAsArray() });
|
||||
}
|
||||
newState.setController(this);
|
||||
this.state=newState;
|
||||
newState.enterState();
|
||||
on.emit(window, "enterState", { bubbles: true, cancelable: true, state: this.state.stateNameAsArray() });
|
||||
},
|
||||
|
||||
entityMouseEvent:function(event,entityUI) {
|
||||
if (!this.state) { return; }
|
||||
var newState=this.state.processMouseEvent(event,entityUI);
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
246
js/iD/Entity.js
Executable file
246
js/iD/Entity.js
Executable file
@@ -0,0 +1,246 @@
|
||||
// iD/Entity.js
|
||||
// Entity classes for iD
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array',
|
||||
'iD/actions/AddNodeToWayAction'
|
||||
], function(declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Entity base class
|
||||
|
||||
declare("iD.Entity", null, {
|
||||
connection: null,
|
||||
id: NaN,
|
||||
loaded: false,
|
||||
tags: null,
|
||||
entityType: '',
|
||||
parents: null,
|
||||
modified: false,
|
||||
deleted: false,
|
||||
|
||||
constructor:function() {
|
||||
this.tags={};
|
||||
this.parents=new Hashtable();
|
||||
},
|
||||
|
||||
isType:function(_type) {
|
||||
return this.entityType==_type;
|
||||
},
|
||||
|
||||
getTagsHash:function() {
|
||||
return this.tags;
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
return this.entityType+"."+this.id;
|
||||
},
|
||||
|
||||
// Provoke redraw and other changes
|
||||
|
||||
refresh:function() { this.connection.refreshEntity(this); },
|
||||
|
||||
// Clean and dirty (only called from UndoableEntityAction)
|
||||
|
||||
markClean:function() { this.modified=false; },
|
||||
markDirty:function() { this.modified=true; },
|
||||
isDirty:function() { return this.modified; },
|
||||
|
||||
// Deletion
|
||||
|
||||
setDeletedState:function(isDeleted) { this.deleted=isDeleted; },
|
||||
|
||||
// Bounding box check (to be overridden)
|
||||
|
||||
within:function(left,right,top,bottom) { return !this.deleted; },
|
||||
|
||||
// Parent-handling
|
||||
|
||||
addParent:function(_entity) {
|
||||
this.parents.put(_entity,true);
|
||||
},
|
||||
removeParent:function(_entity) {
|
||||
this.parents.remove(_entity);
|
||||
},
|
||||
hasParent:function(_entity) {
|
||||
return this.parents.containsKey(_entity);
|
||||
},
|
||||
parentObjects:function() {
|
||||
return this.parents.keys();
|
||||
},
|
||||
hasParentWays:function() {
|
||||
var p=this.parentObjects();
|
||||
for (var i in p) {
|
||||
if (p[i].entityType=='way') { return true; }
|
||||
}
|
||||
return false;
|
||||
},
|
||||
parentWays:function() {
|
||||
return this.parentObjectsOfClass('way');
|
||||
},
|
||||
parentRelations:function() {
|
||||
return this.parentObjectsOfClass('relation');
|
||||
},
|
||||
parentObjectsOfClass:function(_class) {
|
||||
var p=this.parentObjects(), c=[];
|
||||
for (var i in p) {
|
||||
if (p[i].entityType==_class) { c.push(p[i]); }
|
||||
}
|
||||
return c;
|
||||
},
|
||||
// Halcyon also implements:
|
||||
// removeFromParents()
|
||||
// hasParents()
|
||||
// findParentRelationsOfType(type,role)
|
||||
// getRelationMemberships()
|
||||
// countParentObjects(within)
|
||||
// memberships()
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Node class
|
||||
|
||||
declare("iD.Node", [iD.Entity], {
|
||||
lat:NaN,
|
||||
latp:NaN,
|
||||
lon:NaN,
|
||||
entityType:"node",
|
||||
|
||||
constructor:function(_conn,_id,_lat,_lon,_tags,_loaded) {
|
||||
this.connection=_conn;
|
||||
this.id=Number(_id);
|
||||
this.lat=Number(_lat);
|
||||
this.lon=Number(_lon);
|
||||
this.tags=_tags;
|
||||
this.loaded=(_loaded==undefined) ? true : _loaded;
|
||||
this.project();
|
||||
this.modified=this.id<0;
|
||||
},
|
||||
|
||||
project:function() {
|
||||
this.latp=180/Math.PI * Math.log(Math.tan(Math.PI/4+this.lat*(Math.PI/180)/2));
|
||||
},
|
||||
|
||||
within:function(left,right,top,bottom) { return (this.lon>=left) && (this.lon<=right) && (this.lat>=bottom) && (this.lat<=top) && !this.deleted; },
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Way class
|
||||
|
||||
declare("iD.Way", [iD.Entity], {
|
||||
nodes: null,
|
||||
entityType: "way",
|
||||
edgel: NaN,
|
||||
edger: NaN,
|
||||
edget: NaN,
|
||||
edgeb: NaN,
|
||||
|
||||
constructor:function(_conn,_id,_nodes,_tags,_loaded) {
|
||||
this.connection=_conn;
|
||||
this.id=Number(_id);
|
||||
this.nodes=_nodes;
|
||||
this.tags=_tags;
|
||||
this.loaded=(_loaded==undefined) ? true : _loaded;
|
||||
this.modified=this.id<0;
|
||||
var w=this; array.forEach(_nodes,function(node) {
|
||||
node.addParent(w);
|
||||
});
|
||||
this.calculateBbox();
|
||||
},
|
||||
|
||||
length:function() {
|
||||
return this.nodes.length;
|
||||
},
|
||||
|
||||
isClosed:function() {
|
||||
return this.nodes[this.nodes.length-1]==this.nodes[0];
|
||||
},
|
||||
|
||||
isType:function(_type) {
|
||||
switch (_type) {
|
||||
case 'way': return true;
|
||||
case 'area': return this.isClosed;
|
||||
case 'line': return !(this.isClosed);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getNode:function(index) { return this.nodes[index]; },
|
||||
getFirstNode:function() { return this.nodes[0]; },
|
||||
getLastNode:function() { return this.nodes[this.nodes.length-1]; },
|
||||
|
||||
// Bounding-box handling
|
||||
|
||||
within:function(left,right,top,bottom) {
|
||||
if (!this.edgel ||
|
||||
(this.edgel<left && this.edger<left ) ||
|
||||
(this.edgel>right && this.edger>right ) ||
|
||||
(this.edgeb<bottom && this.edget<bottom) ||
|
||||
(this.edgeb>top && this.edgeb>top ) || this.deleted) { return false; }
|
||||
return true;
|
||||
},
|
||||
|
||||
calculateBbox:function() {
|
||||
this.edgel=999999; this.edger=-999999;
|
||||
this.edgeb=999999; this.edget=-999999;
|
||||
for (var i in this.nodes) { this.expandBbox(this.nodes[i]); }
|
||||
},
|
||||
|
||||
expandBbox:function(node) {
|
||||
this.edgel=Math.min(this.edgel,node.lon);
|
||||
this.edger=Math.max(this.edger,node.lon);
|
||||
this.edgeb=Math.min(this.edgeb,node.lat);
|
||||
this.edget=Math.max(this.edget,node.lat);
|
||||
},
|
||||
|
||||
// Action callers
|
||||
|
||||
doAppendNode:function(node, performAction) {
|
||||
if (node!=this.getLastNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, -1, true));
|
||||
return this.nodes.length + 1;
|
||||
},
|
||||
|
||||
doPrependNode:function(node, performAction) {
|
||||
if (node!=this.getFirstNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, 0, true));
|
||||
return this.nodes.length + 1;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Relation class
|
||||
|
||||
declare("iD.Relation", [iD.Entity], {
|
||||
members:null,
|
||||
entityType:"relation",
|
||||
|
||||
constructor:function(_conn,_id,_members,_tags,_loaded) {
|
||||
this.connection=_conn;
|
||||
this.id=Number(_id);
|
||||
this.members=_members;
|
||||
this.tags=_tags;
|
||||
this.modified=this.id<0;
|
||||
this.loaded=(_loaded==undefined) ? true : _loaded;
|
||||
var r=this; array.forEach(_members,function(member) {
|
||||
member.entity.addParent(r);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// RelationMember class
|
||||
|
||||
declare("iD.RelationMember", [], {
|
||||
entity:null,
|
||||
role:"",
|
||||
constructor:function(_entity,_role) {
|
||||
this.entity=_entity;
|
||||
this.role=_role;
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
63
js/iD/actions/AddNodeToWayAction.js
Normal file
63
js/iD/actions/AddNodeToWayAction.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// iD/actions/AddNodeToWayAction.js
|
||||
|
||||
define(['dojo/_base/declare','iD/actions/UndoableEntityAction'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// AddNodeToWayAction class
|
||||
|
||||
declare("iD.actions.AddNodeToWayAction", [iD.actions.UndoableEntityAction], {
|
||||
|
||||
node: null,
|
||||
nodeList: null,
|
||||
index: 0,
|
||||
firstNode: null,
|
||||
autoDelete: true,
|
||||
|
||||
constructor:function(_way, _node, _nodeList, _index, _autoDelete) {
|
||||
this.entity = _way;
|
||||
this.node = _node;
|
||||
this.nodeList = _nodeList;
|
||||
this.index = _index;
|
||||
this.autoDelete = _autoDelete;
|
||||
},
|
||||
|
||||
doAction:function() {
|
||||
var way=this.entity; // shorthand
|
||||
|
||||
// undelete way if it was deleted before (only happens on redo)
|
||||
if (way.deleted) {
|
||||
way.setDeletedState(false);
|
||||
if (!this.firstNode.hasParentWays()) this.firstNode.connection.unregisterPOI(firstNode);
|
||||
this.firstNode.addParent(way);
|
||||
}
|
||||
|
||||
// add the node
|
||||
if (this.index==-1) this.index=this.nodeList.length;
|
||||
this.node.addParent(way);
|
||||
this.nodeList.splice(this.index, 0, this.node);
|
||||
this.markDirty();
|
||||
// way.expandBbox(this.node);
|
||||
way.refresh();
|
||||
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
undoAction:function() {
|
||||
var way=this.entity; // shorthand
|
||||
if (this.autoDelete && way.length()==2 && way.parentRelations().length()) return this.FAIL;
|
||||
|
||||
// remove node
|
||||
var removed=nodeList.splice(index, 1);
|
||||
if (this.nodeList.indexOf(removed[0])==-1) { removed[0].removeParent(way); }
|
||||
this.markClean();
|
||||
way.refresh();
|
||||
|
||||
// ** if the way is now 1-length, we should do something like deleting it and
|
||||
// converting the remaining node to a POI (see P2)
|
||||
return this.SUCCESS;
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
49
js/iD/actions/CreateEntityAction.js
Normal file
49
js/iD/actions/CreateEntityAction.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// iD/actions/UndoableAction.js
|
||||
|
||||
define(['dojo/_base/declare','iD/actions/UndoableAction'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// CreateEntityAction class
|
||||
|
||||
declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], {
|
||||
|
||||
setCreate:null,
|
||||
deleteAction:null,
|
||||
|
||||
// When undo is called, instead of simply removing the entity, we work through
|
||||
// to make a Delete[Entity]Action, call that, and store it for later
|
||||
// Then, when this action is called again (i.e. a redo), instead of creating yet another entity, we call the deleteAction.undoAction
|
||||
|
||||
constructor:function(entity,setCreate) {
|
||||
this.setCreate = setCreate;
|
||||
this.setName("Create "+entity.entityType);
|
||||
},
|
||||
|
||||
doAction:function() {
|
||||
if (this.deleteAction!=null) {
|
||||
this.deleteAction.undoAction(); // redo
|
||||
} else {
|
||||
this.setCreate(this.entity, false); // first time
|
||||
}
|
||||
this.markDirty();
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
undoAction:function() {
|
||||
// if the undo is called for the first time, call for a deletion, and (via setAction) store the
|
||||
// deletion action for later. We'll undo the deletion if we get asked to redo this action
|
||||
if (this.deleteAction==null) { this.entity.remove(this.setAction); }
|
||||
this.deleteAction.doAction();
|
||||
this.markClean();
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
setAction:function(action) {
|
||||
deleteAction = action;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
47
js/iD/actions/CreatePOIAction.js
Normal file
47
js/iD/actions/CreatePOIAction.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// iD/actions/CreatePOIAction.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','iD/actions/UndoableAction'], function(declare,lang){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// CreatePOIAction class
|
||||
|
||||
declare("iD.actions.CreatePOIAction", [iD.actions.CompositeUndoableAction], {
|
||||
|
||||
newNode: null,
|
||||
tags: null,
|
||||
lat: NaN,
|
||||
lon: NaN,
|
||||
connection: null,
|
||||
|
||||
constructor:function(connection,tags,lat,lon) {
|
||||
this.setName("Create POI");
|
||||
this.connection = connection;
|
||||
this.tags = tags;
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
},
|
||||
|
||||
doAction:function() {
|
||||
if (this.newNode==null) {
|
||||
this.newNode=this.connection.createNode(this.tags,this.lat,this.lon,lang.hitch(this,this.push));
|
||||
}
|
||||
this.inherited(arguments);
|
||||
this.connection.registerPOI(this.newNode);
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
undoAction:function() {
|
||||
this.inherited(arguments);
|
||||
this.connection.unregisterPOI(this.newNode);
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
getNode:function() {
|
||||
return this.newNode;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
110
js/iD/actions/UndoStack.js
Normal file
110
js/iD/actions/UndoStack.js
Normal file
@@ -0,0 +1,110 @@
|
||||
// iD/actions/UndoStack.js
|
||||
|
||||
define(['dojo/_base/declare'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// UndoStack base class
|
||||
|
||||
declare("iD.actions.UndoStack", null, {
|
||||
|
||||
undoActions: null,
|
||||
redoActions: null,
|
||||
|
||||
FAIL: 0,
|
||||
SUCCESS: 1,
|
||||
NO_CHANGE: 2,
|
||||
|
||||
constructor:function() {
|
||||
this.undoActions=[];
|
||||
this.redoActions=[];
|
||||
},
|
||||
|
||||
addAction:function(_action) {
|
||||
var result = _action.doAction();
|
||||
switch (result) {
|
||||
case this.FAIL:
|
||||
// do something bad
|
||||
break;
|
||||
|
||||
case this.NO_CHANGE:
|
||||
break;
|
||||
|
||||
case this.SUCCESS:
|
||||
default:
|
||||
if (this.undoActions.length>0) {
|
||||
var previous = this.undoActions[this.undoActions.length-1];
|
||||
if (_action.mergePrevious(previous)) {
|
||||
_action.wasDirty = previous.wasDirty;
|
||||
_action.connectionWasDirty = previous.connectionWasDirty;
|
||||
this.undoActions.pop();
|
||||
}
|
||||
}
|
||||
this.undoActions.push(_action);
|
||||
this.redoActions=[];
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
breakUndo:function() {
|
||||
this.undoActions = [];
|
||||
this.redoActions = [];
|
||||
},
|
||||
|
||||
canUndo:function() {
|
||||
return this.undoActions.length > 0;
|
||||
},
|
||||
|
||||
canRedo:function() {
|
||||
return this.redoActions.length > 0;
|
||||
},
|
||||
|
||||
// Undo the most recent action, and add it to the top of the redo stack
|
||||
undo:function() {
|
||||
if (!this.undoActions.length) { return; }
|
||||
var action = undoActions.pop();
|
||||
action.undoAction();
|
||||
redoActions.push(action);
|
||||
},
|
||||
undoIfAction:function(_action) {
|
||||
if (!this.undoActions.length) { return; }
|
||||
if (this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) {
|
||||
this.undo();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
removeLastIfAction:function(_action) {
|
||||
if (this.undoActions.length && this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) {
|
||||
this.undoActions.pop();
|
||||
}
|
||||
},
|
||||
|
||||
getUndoDescription:function() {
|
||||
if (this.undoActions.length==0) return null;
|
||||
if (this.undoActions[this.undoActions.length-1].name) {
|
||||
return this.undoActions[this.undoActions.length-1].name;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getRedoDescription:function() {
|
||||
if (this.redoActions.length==0) return null;
|
||||
if (this.redoActions[this.redoActions.length-1].name) {
|
||||
return this.redoActions[this.redoActions.length-1].name;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Takes the action most recently undone, does it, and adds it to the undo stack
|
||||
redo:function() {
|
||||
if (!this.redoActions.length) { return; }
|
||||
var action = this.redoActions.pop();
|
||||
action.doAction();
|
||||
this.undoActions.push(action);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
135
js/iD/actions/UndoableAction.js
Normal file
135
js/iD/actions/UndoableAction.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// iD/actions/UndoableAction.js
|
||||
// we don't currently do connectionWasDirty, and we should
|
||||
|
||||
define(['dojo/_base/declare'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// UndoableAction base class
|
||||
|
||||
declare("iD.actions.UndoableAction", null, {
|
||||
|
||||
FAIL: 0,
|
||||
SUCCESS: 1,
|
||||
NO_CHANGE: 2,
|
||||
|
||||
name: "",
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
doAction:function() {
|
||||
return FAIL;
|
||||
},
|
||||
|
||||
undoAction:function() {
|
||||
return FAIL;
|
||||
},
|
||||
|
||||
mergePrevious:function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
setName:function(_name) {
|
||||
this.name=_name;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// UndoableEntityAction class
|
||||
|
||||
declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], {
|
||||
|
||||
wasDirty: false,
|
||||
connectionWasDirty: false,
|
||||
initialised: false,
|
||||
entity: null,
|
||||
|
||||
constructor:function(_entity) {
|
||||
this.entity=_entity;
|
||||
},
|
||||
|
||||
markDirty:function() {
|
||||
if (!this.initialised) { this.init(); }
|
||||
if (!this.wasDirty) { this.entity.markDirty(); }
|
||||
// if (!this.connectionWasDirty ) { this.entity.connection.markDirty(); }
|
||||
},
|
||||
|
||||
markClean:function() {
|
||||
if (!this.initialised) { this.init(); }
|
||||
if (!this.wasDirty) { this.entity.markClean(); }
|
||||
// if (!connectionWasDirty) { this.entity.connection.markClean(); }
|
||||
},
|
||||
|
||||
// Record whether or not the entity and connection were clean before this action started
|
||||
init:function() {
|
||||
this.wasDirty = this.entity.isDirty;
|
||||
// this.connectionWasDirty = this.entity.connection.isDirty;
|
||||
this.initialised = true;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// UndoableEntityAction class
|
||||
|
||||
declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
|
||||
|
||||
actions: null,
|
||||
actionsDone: false,
|
||||
|
||||
constructor:function() {
|
||||
this.actions=[];
|
||||
},
|
||||
|
||||
push:function(action) {
|
||||
this.actions.push(action);
|
||||
},
|
||||
|
||||
clearActions:function() {
|
||||
this.actions=[];
|
||||
},
|
||||
|
||||
doAction:function() {
|
||||
if (this.actionsDone) { return this.FAIL; }
|
||||
var somethingDone=false;
|
||||
for (var i=0; i<this.actions.length; i++) {
|
||||
var action = this.actions[i];
|
||||
var result = action.doAction();
|
||||
switch (result) {
|
||||
case this.NO_CHANGE:
|
||||
// splice this one out as it doesn't do anything
|
||||
this.actions.splice(i,1);
|
||||
i--;
|
||||
break;
|
||||
case this.FAIL:
|
||||
this.undoFrom(i);
|
||||
return this.FAIL;
|
||||
default:
|
||||
somethingDone=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.actionsDone = true;
|
||||
return somethingDone ? this.SUCCESS : this.NO_CHANGE;
|
||||
},
|
||||
|
||||
undoAction:function() {
|
||||
if (!this.actionsDone) { return this.FAIL; }
|
||||
this.undoFrom(this.actions.length);
|
||||
return this.SUCCESS;
|
||||
},
|
||||
|
||||
undoFrom:function(index) {
|
||||
for (var i=index-1; i>=0; i--) {
|
||||
this.actions[i].undoAction();
|
||||
}
|
||||
this.actionsDone=false;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
45
js/iD/actions/UndoableEntityAction.js
Normal file
45
js/iD/actions/UndoableEntityAction.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// iD/actions/UndoableEntityAction.js
|
||||
|
||||
define(['dojo/_base/declare','iD/actions/UndoableAction'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// UndoableEntityAction class
|
||||
|
||||
declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], {
|
||||
|
||||
wasDirty: false,
|
||||
connectionWasDirty: false,
|
||||
initialised: false,
|
||||
entity: null,
|
||||
|
||||
constructor:function(_entity) {
|
||||
this.entity = _entity;
|
||||
},
|
||||
|
||||
markDirty:function() {
|
||||
if (!this.initialised) this.init();
|
||||
if (!this.wasDirty) this.entity.markDirty();
|
||||
if (!this.connectionWasDirty) this.entity.connection.markDirty();
|
||||
},
|
||||
|
||||
markClean:function() {
|
||||
if (!this.initialised) this.init();
|
||||
if (!this.wasDirty) this.entity.markClean();
|
||||
if (!this.connectionWasDirty) this.entity.connection.markClean();
|
||||
},
|
||||
|
||||
// Record whether or not the entity and connection were clean before this action started
|
||||
init:function() {
|
||||
this.wasDirty = this.entity.isDirty();
|
||||
this.connectionWasDirty = this.entity.connection.isDirty();
|
||||
this.initialised = true;
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
return this.name + " " + this.entity.entityType + " " + this.entity.id;
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
49
js/iD/controller/ControllerState.js
Executable file
49
js/iD/controller/ControllerState.js
Executable file
@@ -0,0 +1,49 @@
|
||||
// iD/controller/ControllerState.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang'], function(declare,lang) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ControllerState base class
|
||||
|
||||
declare("iD.controller.ControllerState", null, {
|
||||
controller: null, // parent Controller
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
setController:function(_controller) {
|
||||
this.controller=_controller;
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
},
|
||||
|
||||
enterState:function() {
|
||||
},
|
||||
|
||||
exitState:function(newState) {
|
||||
},
|
||||
|
||||
stateName:function() {
|
||||
return this.stateNameAsArray.join('.');
|
||||
},
|
||||
|
||||
stateNameAsArray:function() {
|
||||
return this.__proto__.declaredClass.split('.').slice(2);
|
||||
},
|
||||
|
||||
getConnection:function() {
|
||||
return this.controller.map.conn;
|
||||
},
|
||||
|
||||
undoAdder:function() {
|
||||
/* This is a convenient shorthand for adding an action to the global undo stack,
|
||||
setting the scope correctly. */
|
||||
return lang.hitch(this.controller.undoStack, this.controller.undoStack.addAction);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
39
js/iD/controller/edit/NoSelection.js
Executable file
39
js/iD/controller/edit/NoSelection.js
Executable file
@@ -0,0 +1,39 @@
|
||||
// iD/controller/edit/NoSelection.js
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/controller/ControllerState',
|
||||
'iD/controller/edit/SelectedWay',
|
||||
'iD/controller/edit/SelectedWayNode',
|
||||
'iD/controller/edit/SelectedPOINode',
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ControllerState base class
|
||||
|
||||
declare("iD.controller.edit.NoSelection", [iD.controller.ControllerState], {
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
this.inherited(arguments);
|
||||
if (!entityUI) { return this; }
|
||||
var entity=entityUI.entity;
|
||||
if (event.type=='click') {
|
||||
switch (entity.entityType) {
|
||||
case 'node':
|
||||
var ways=entity.parentWays();
|
||||
if (ways.length==0) { return new iD.controller.edit.SelectedPOINode(entity); }
|
||||
else { return new iD.controller.edit.SelectedWayNode(entity,ways[0]); }
|
||||
case 'way':
|
||||
return new iD.controller.edit.SelectedWay(entityUI.entity);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
44
js/iD/controller/edit/SelectedPOINode.js
Executable file
44
js/iD/controller/edit/SelectedPOINode.js
Executable file
@@ -0,0 +1,44 @@
|
||||
// iD/controller/edit/SelectedPOINode.js
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/ControllerState'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedPOINode class
|
||||
|
||||
declare("iD.controller.edit.SelectedPOINode", [iD.controller.ControllerState], {
|
||||
|
||||
node: null,
|
||||
nodeUI: null,
|
||||
|
||||
constructor:function(_node) {
|
||||
this.node=_node;
|
||||
},
|
||||
enterState:function() {
|
||||
this.nodeUI=this.controller.map.getUI(this.node);
|
||||
this.nodeUI.setStateClass('selected');
|
||||
this.nodeUI.redraw();
|
||||
},
|
||||
exitState:function() {
|
||||
this.nodeUI.resetStateClass('selected');
|
||||
this.nodeUI.redraw();
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
if (event.type=='click') {
|
||||
switch (entityType) {
|
||||
case null: return new iD.controller.edit.NoSelection();
|
||||
case 'node': return new iD.controller.edit.SelectedPOINode(entityUI.entity);
|
||||
case 'way': return new iD.controller.edit.SelectedWay(entityUI.entity);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
53
js/iD/controller/edit/SelectedWay.js
Executable file
53
js/iD/controller/edit/SelectedWay.js
Executable file
@@ -0,0 +1,53 @@
|
||||
// iD/controller/edit/SelectedWay.js
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/ControllerState'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWay class
|
||||
|
||||
declare("iD.controller.edit.SelectedWay", [iD.controller.ControllerState], {
|
||||
|
||||
way: null,
|
||||
wayUI: null,
|
||||
|
||||
constructor:function(_way) {
|
||||
this.way=_way;
|
||||
},
|
||||
enterState:function() {
|
||||
this.wayUI=this.controller.map.getUI(this.way);
|
||||
this.wayUI.setStateClass('selected');
|
||||
this.wayUI.setStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
},
|
||||
exitState:function() {
|
||||
this.wayUI.resetStateClass('selected');
|
||||
this.wayUI.resetStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
if (event.type=='click') {
|
||||
switch (entityType) {
|
||||
case null:
|
||||
return new iD.controller.edit.NoSelection();
|
||||
case 'node':
|
||||
var ways=entity.parentWays();
|
||||
if (entity.hasParent(this.way)) { return new iD.controller.edit.SelectedWayNode(entity,this.way); }
|
||||
else if (ways.length==0) { return new iD.controller.edit.SelectedPOINode(entity); }
|
||||
else { return new iD.controller.edit.SelectedWayNode(entity,ways[0]); }
|
||||
case 'way':
|
||||
return new iD.controller.edit.SelectedWay(entityUI.entity);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
50
js/iD/controller/edit/SelectedWayNode.js
Executable file
50
js/iD/controller/edit/SelectedWayNode.js
Executable file
@@ -0,0 +1,50 @@
|
||||
// iD/controller/edit/SelectedWayNode.js
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/ControllerState'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWayNode class
|
||||
|
||||
declare("iD.controller.edit.SelectedWayNode", [iD.controller.ControllerState], {
|
||||
|
||||
node: null,
|
||||
way: null,
|
||||
|
||||
constructor:function(_node,_way) {
|
||||
this.node=_node;
|
||||
this.way=_way;
|
||||
},
|
||||
enterState:function() {
|
||||
this.controller.map.getUI(this.way ).setStateClass('shownodes').redraw();
|
||||
this.controller.map.getUI(this.node).setStateClass('selected' ).redraw();
|
||||
},
|
||||
exitState:function() {
|
||||
this.controller.map.getUI(this.way ).resetStateClass('shownodes').redraw();
|
||||
this.controller.map.getUI(this.node).resetStateClass('selected' ).redraw();
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
if (event.type=='click') {
|
||||
switch (entityType) {
|
||||
case null:
|
||||
return new iD.controller.edit.NoSelection();
|
||||
case 'node':
|
||||
var ways=entity.parentWays();
|
||||
if (entity.hasParent(this.way)) { return new iD.controller.edit.SelectedWayNode(entity,this.way); }
|
||||
else if (ways.length==0) { return new iD.controller.edit.SelectedPOINode(entity); }
|
||||
else { return new iD.controller.edit.SelectedWayNode(entity,ways[0]); }
|
||||
case 'way':
|
||||
return new iD.controller.edit.SelectedWay(entityUI.entity);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
165
js/iD/controller/shape/DrawWay.js
Normal file
165
js/iD/controller/shape/DrawWay.js
Normal file
@@ -0,0 +1,165 @@
|
||||
// iD/controller/shape/DrawWay.js
|
||||
|
||||
/*
|
||||
Add road or shape -> DrawWay
|
||||
|
||||
The user is drawing a way.
|
||||
|
||||
Goes to:
|
||||
-> click empty area: adds point, continues
|
||||
-> click way: adds junction, continues
|
||||
-> click node: adds to way, continues
|
||||
-> double-click, or click this way: goes to Edit/SelectedWay
|
||||
|
||||
*/
|
||||
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','dojox/gfx/shape','iD/controller/ControllerState'], function(declare,lang,shape){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// DrawWay class
|
||||
|
||||
declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
|
||||
|
||||
way: null,
|
||||
wayUI: null,
|
||||
editEnd: false,
|
||||
|
||||
constructor:function(_way) {
|
||||
this.way=_way;
|
||||
},
|
||||
enterState:function() {
|
||||
this.wayUI=this.controller.map.getUI(this.way);
|
||||
this.wayUI.setStateClass('selected');
|
||||
this.wayUI.setStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
this.controller.stepper.highlight(2);
|
||||
},
|
||||
exitState:function() {
|
||||
this.controller.map.clearElastic();
|
||||
this.wayUI.resetStateClass('selected');
|
||||
this.wayUI.resetStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
var map=this.controller.map;
|
||||
|
||||
if (event.type=='mouseover' && entityType=='way' && entityUI!=this.wayUI) {
|
||||
// Mouse over way, show hover highlight
|
||||
entityUI.setStateClass('shownodeshover');
|
||||
entityUI.redraw();
|
||||
this.wayUI.redraw();
|
||||
this.updateElastic(event);
|
||||
return this;
|
||||
|
||||
} else if (event.type=='mouseout' && entityType=='way' && entityUI!=this.wayUI) {
|
||||
// Mouse left way, remove hover highlight
|
||||
// Find what object we're moving into
|
||||
var into=shape.byId((event.hasOwnProperty('toElement') ? event.toElement : event.relatedTarget).__gfxObject__);
|
||||
// If it's a nodeUI that belongs to a hovering way, don't deselect
|
||||
if (into && into.hasOwnProperty('source') && into.source.hasStateClass('hoverway') && into.source.entity.hasParent(entity)) { return this; }
|
||||
entityUI.resetStateClass('shownodeshover');
|
||||
entityUI.redraw();
|
||||
this.wayUI.redraw();
|
||||
this.updateElastic(event);
|
||||
return this;
|
||||
|
||||
} else if (event.type=='mouseout' && entityType=='node') {
|
||||
// Mouse left node, remove hover highlight from parent way too
|
||||
var ways=entity.parentWays();
|
||||
for (var i in ways) {
|
||||
var ui=this.controller.map.getUI(ways[i]);
|
||||
if (ui && ui.hasStateClass('shownodeshover')) {
|
||||
ui.resetStateClass('shownodeshover');
|
||||
ui.redraw();
|
||||
}
|
||||
}
|
||||
this.updateElastic(event);
|
||||
this.wayUI.redraw();
|
||||
return this;
|
||||
|
||||
} else if (event.type=='mousemove') {
|
||||
// Mouse moved, update elastic
|
||||
this.updateElastic(event);
|
||||
return this;
|
||||
|
||||
} else if (event.type=='mousedown') {
|
||||
switch (entityType) {
|
||||
case 'node':
|
||||
// Click on node
|
||||
if (entity==this.getDrawingNode()) {
|
||||
// Double-click, so complete drawing
|
||||
return new iD.controller.edit.SelectedWay(this.way);
|
||||
} else if (entity==this.getStartNode()) {
|
||||
// Start of this way, so complete drawing
|
||||
this.appendNode(entity, this.undoAdder() );
|
||||
return new iD.controller.edit.SelectedWay(this.way);
|
||||
} else {
|
||||
// Add to way
|
||||
this.appendNode(entity, this.undoAdder() );
|
||||
return this;
|
||||
}
|
||||
|
||||
case 'way':
|
||||
// Click on way, add new junction node to way
|
||||
console.log("clicked a way, add new junction to way");
|
||||
var ways=[entity]; // ** needs to find all the ways under the mouse
|
||||
var undo=new iD.actions.CompositeUndoableAction();
|
||||
var node=this.appendNewNode(event, undo);
|
||||
// array.forEach(ways, function(w) { w.insertNodeAtClosestPosition(node, true, undo.push); } );
|
||||
var action=this.undoAdder(); action(undo);
|
||||
return this;
|
||||
}
|
||||
|
||||
} else if (event.type=='click') {
|
||||
// Click on empty space, add new node to way
|
||||
console.log("clicked empty space, add a new node to way");
|
||||
var undo=new iD.actions.CompositeUndoableAction();
|
||||
this.appendNewNode(event, undo);
|
||||
var action=this.undoAdder(); action(undo);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
updateElastic:function(event) {
|
||||
var map=this.controller.map;
|
||||
map.drawElastic(
|
||||
map.lon2coord(this.getDrawingNode().lon),
|
||||
map.lat2coord(this.getDrawingNode().lat),
|
||||
map.mouseX(event), map.mouseY(event)
|
||||
);
|
||||
},
|
||||
|
||||
getDrawingNode:function() {
|
||||
return (this.editEnd ? this.way.nodes[this.way.length()-1] : this.way.nodes[0]);
|
||||
},
|
||||
|
||||
getStartNode:function() {
|
||||
return (this.editEnd ? this.way.nodes[0] : this.way.nodes[this.way.length()-1]);
|
||||
},
|
||||
|
||||
appendNode:function(node, performAction) {
|
||||
if (this.editEnd) { this.way.doAppendNode(node, performAction); }
|
||||
else { this.way.doPrependNode(node, performAction); }
|
||||
},
|
||||
|
||||
appendNewNode:function(event, undo) {
|
||||
var map=this.controller.map;
|
||||
var node=this.getConnection().createNode(
|
||||
{},
|
||||
map.coord2lat(map.mouseY(event)),
|
||||
map.coord2lon(map.mouseX(event)), lang.hitch(undo,undo.push) );
|
||||
this.appendNode(node, lang.hitch(undo,undo.push));
|
||||
return node;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
78
js/iD/controller/shape/NoSelection.js
Normal file
78
js/iD/controller/shape/NoSelection.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// iD/controller/shape/NoSelection.js
|
||||
|
||||
/*
|
||||
Add road or shape -> NoSelection
|
||||
|
||||
The user has clicked 'Add road or shape', but hasn't yet started drawing.
|
||||
|
||||
Goes to:
|
||||
-> click empty area: goes to shape/DrawWay
|
||||
-> click way: goes to shape/SelectedWay
|
||||
-> click way-node: goes to shape/SelectedWayNode
|
||||
-> click POI: ** not done yet, needs to ask "convert to shape"?
|
||||
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState',
|
||||
'iD/controller/shape/DrawWay',
|
||||
'iD/controller/shape/SelectedWay',
|
||||
'iD/controller/shape/SelectedWayNode',
|
||||
'iD/controller/shape/SelectedPOINode',
|
||||
], function(declare,lang){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ControllerState base class
|
||||
|
||||
declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
enterState:function() {
|
||||
this.controller.stepper.setSteps([
|
||||
"Click anywhere on the map to start drawing there",
|
||||
"Keep clicking to add each point, and press Enter or double-click when you're done",
|
||||
"Set the type of the road or shape"
|
||||
]).highlight(1);
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
var map=this.controller.map;
|
||||
|
||||
if (event.type=='click') {
|
||||
switch (entityType) {
|
||||
case 'node':
|
||||
// Click to select a node
|
||||
var ways=entity.parentWays();
|
||||
if (ways.length==0) { return new iD.controller.shape.SelectedPOINode(entity); }
|
||||
else { return new iD.controller.shape.SelectedWayNode(entity,ways[0]); }
|
||||
case 'way':
|
||||
// Click to select a way
|
||||
return new iD.controller.shape.SelectedWay(entityUI.entity);
|
||||
default:
|
||||
// Click to start a new way
|
||||
var undo = new iD.actions.CompositeUndoableAction();
|
||||
console.log("Event is ",event.type);
|
||||
var startNode = this.getConnection().createNode(
|
||||
{},
|
||||
map.coord2lat(map.mouseY(event)),
|
||||
map.coord2lon(map.mouseX(event)), lang.hitch(undo,undo.push) );
|
||||
var way = this.getConnection().createWay({}, [startNode], lang.hitch(undo,undo.push) );
|
||||
this.controller.undoStack.addAction(undo);
|
||||
this.controller.map.createUI(way);
|
||||
console.log("Started new way");
|
||||
return new iD.controller.shape.DrawWay(way);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
34
js/iD/controller/shape/SelectedPOINode.js
Normal file
34
js/iD/controller/shape/SelectedPOINode.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// iD/controller/shape/SelectedPOINode.js
|
||||
|
||||
/*
|
||||
Add road or shape -> SelectedPOINode
|
||||
|
||||
The user has clicked 'Add road or shape', then a POI node to be converted to a way.
|
||||
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState',
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedPOINode class
|
||||
|
||||
declare("iD.controller.shape.SelectedPOINode", [iD.controller.ControllerState], {
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
34
js/iD/controller/shape/SelectedWay.js
Normal file
34
js/iD/controller/shape/SelectedWay.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// iD/controller/shape/SelectedWay.js
|
||||
|
||||
/*
|
||||
Add road or shape -> SelectedWay
|
||||
|
||||
The user has clicked 'Add road or shape', then a way to start the new way at.
|
||||
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState',
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWayNode class
|
||||
|
||||
declare("iD.controller.shape.SelectedWay", [iD.controller.ControllerState], {
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
34
js/iD/controller/shape/SelectedWayNode.js
Normal file
34
js/iD/controller/shape/SelectedWayNode.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// iD/controller/shape/SelectedWayNode.js
|
||||
|
||||
/*
|
||||
Add road or shape -> SelectedWayNode
|
||||
|
||||
The user has clicked 'Add road or shape', then a way-node to start the way at.
|
||||
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState',
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWayNode class
|
||||
|
||||
declare("iD.controller.shape.SelectedWayNode", [iD.controller.ControllerState], {
|
||||
|
||||
constructor:function() {
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
116
js/iD/renderer/EntityUI.js
Executable file
116
js/iD/renderer/EntityUI.js
Executable file
@@ -0,0 +1,116 @@
|
||||
// iD/renderer/EntityUI.js
|
||||
// EntityUI classes for iD
|
||||
// multipolygon support - http://mail.dojotoolkit.org/pipermail/dojo-interest/2011-January/052042.html
|
||||
// support 'interactive'
|
||||
// line decoration, dots etc.
|
||||
// fill images
|
||||
// opacity
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','iD/Entity','iD/renderer/Map'], function(declare,lang){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// EntityUI base class
|
||||
|
||||
declare("iD.renderer.EntityUI", null, {
|
||||
entity:null, // Entity this represents
|
||||
map:null, // the Map object containing this
|
||||
layer:0, // OSM layer
|
||||
sprites:null, // array of sprites created for this EntityUI
|
||||
styleList:null, // current StyleList
|
||||
stateClasses:null, // list of stateClass tags to apply
|
||||
constructor:function(_entity,_map,_stateClasses) {
|
||||
this.entity=_entity;
|
||||
this.map=_map;
|
||||
this.stateClasses=_stateClasses ? _stateClasses.slice() : [];
|
||||
this.sprites=[];
|
||||
},
|
||||
getConnection:function() {
|
||||
return this.map.conn;
|
||||
},
|
||||
targetGroup:function(groupType,sublayer) {
|
||||
return this.map.sublayer(this.layer,groupType,sublayer);
|
||||
},
|
||||
recordSprite:function(sprite) {
|
||||
if (this.sprites.indexOf(sprite)==-1) { this.sprites.push(sprite); }
|
||||
return sprite;
|
||||
},
|
||||
removeSprites:function() {
|
||||
for (var i=0; i<this.sprites.length; i++) {
|
||||
this.sprites[i].removeShape();
|
||||
}
|
||||
this.sprites=[];
|
||||
},
|
||||
refreshStyleList:function(tags) {
|
||||
if (!this.styleList || !this.styleList.isValidAt(this.map.scale)) {
|
||||
this.styleList=this.map.ruleset.getStyles(this.entity,tags,this.map.scale);
|
||||
}
|
||||
this.layer=this.styleList.layerOverride();
|
||||
if (isNaN(this.layer)) {
|
||||
this.layer=0;
|
||||
if (tags['layer']) { this.layer=Number(tags['layer']); }
|
||||
}
|
||||
|
||||
// Iterate through each subpart, drawing any styles on that layer
|
||||
var drawn=false;
|
||||
for (i=0; i<this.styleList.subparts.length; i++) {
|
||||
var subpart=this.styleList.subparts[i];
|
||||
}
|
||||
},
|
||||
getEnhancedTags:function() {
|
||||
// Clone entity tags
|
||||
var tags=lang.clone(this.entity.tags);
|
||||
// Apply stateClasses (hover, selected, hoverway, selectedway)
|
||||
for (var i in this.stateClasses) {
|
||||
tags[':'+this.stateClasses[i]]='yes';
|
||||
}
|
||||
// todo - Add any common 'special-case' tags, e.g. :hasTags
|
||||
return tags;
|
||||
},
|
||||
|
||||
// State class handling
|
||||
|
||||
// Set all state classes at once, and prompt a redraw if they're different to previously
|
||||
setStateClasses:function(_stateClasses) {
|
||||
if (_stateClasses && this.stateClasses.join(',')!=_stateClasses.join(',')) {
|
||||
this.stateClasses=_stateClasses.slice();
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// Set a single state class, and prompt a redraw if it wasn't set previously
|
||||
setStateClass:function(sc) {
|
||||
if (this.stateClasses.indexOf(sc)==-1) {
|
||||
this.stateClasses.push(sc);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// Reset a single state class, and prompt a redraw if it was set previously
|
||||
resetStateClass:function(sc) {
|
||||
if (this.stateClasses.indexOf(sc)>-1) {
|
||||
this.stateClasses.splice(this.stateClasses.indexOf(sc),1);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
hasStateClass:function(sc) {
|
||||
return this.stateClasses.indexOf(sc)>-1;
|
||||
},
|
||||
invalidateStyleList:function() {
|
||||
this.styleList=null;
|
||||
},
|
||||
|
||||
// Mouse event handling
|
||||
|
||||
entityMouseEvent:function(event) {
|
||||
this.map.controller.entityMouseEvent(event, event.gfxTarget.source);
|
||||
event.stopPropagation();
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
448
js/iD/renderer/Map.js
Executable file
448
js/iD/renderer/Map.js
Executable file
@@ -0,0 +1,448 @@
|
||||
// iD/renderer/Map.js
|
||||
// at present this combines P2's Map and MapPaint functionality
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/event','dojo/_base/lang',
|
||||
'dojo/dom-geometry',
|
||||
'dojox/gfx','dojox/gfx/matrix',
|
||||
'iD/Connection','iD/Entity','iD/renderer/EntityUI','iD/renderer/WayUI','iD/renderer/NodeUI'],
|
||||
function(declare,array,Event,lang,domGeom,Gfx,Matrix){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Connection base class
|
||||
|
||||
declare("iD.renderer.Map", null, {
|
||||
|
||||
MASTERSCALE: 5825.4222222222,
|
||||
MINSCALE: 14,
|
||||
MAXSCALE: 23,
|
||||
scale: NaN,
|
||||
scalefactor: NaN,
|
||||
baselon: NaN, // original longitude at top left of viewport
|
||||
baselat: NaN, // original latitude at top left of viewport
|
||||
baselatp: NaN, // original projected latitude at top left of viewport
|
||||
|
||||
div: '', // <div> of this map
|
||||
surface: null, // <div>.surface containing the rendering
|
||||
container: null, // root-level group within the surface
|
||||
backdrop: null, // coloured backdrop (MapCSS canvas element)
|
||||
conn: null, // data store
|
||||
controller: null, // UI controller
|
||||
nodeuis: {}, // graphic representations of data
|
||||
wayuis: {}, // |
|
||||
|
||||
tilegroup: null, // group within container for adding bitmap tiles
|
||||
tiles: [], // index of tile objects
|
||||
tilebaseURL: 'http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z', // Bing imagery URL
|
||||
|
||||
dragging: false, // current drag state
|
||||
dragged: false, // was most recent click a drag?
|
||||
dragx: NaN, // click co-ordinates at previously recorded drag event
|
||||
dragy: NaN, // |
|
||||
startdragx: NaN, // click co-ordinates at start of drag
|
||||
startdragy: NaN, // |
|
||||
dragtime: NaN, // timestamp of mouseup (compared to stop resulting click from firing)
|
||||
dragconnect: null, // event listener for endDrag
|
||||
|
||||
containerx: 0, // screen co-ordinates of container
|
||||
containery: 0, // |
|
||||
centrelat: NaN, // lat/long and bounding box of map
|
||||
centrelon: NaN, // |
|
||||
edgel: NaN, // |
|
||||
edger: NaN, // |
|
||||
edget: NaN, // |
|
||||
edgeb: NaN, // |
|
||||
mapheight: NaN, // size of map object in pixels
|
||||
mapwidth: NaN, // |
|
||||
|
||||
layers: null, // array-like object of Groups, one for each OSM layer
|
||||
minlayer: -5, // minimum OSM layer supported
|
||||
maxlayer: 5, // maximum OSM layer supported
|
||||
|
||||
elastic: null, // Group for drawing elastic band
|
||||
|
||||
ruleset: null, // map style
|
||||
|
||||
// Constructor
|
||||
|
||||
constructor:function(_lat,_lon,_scale,_divname,_conn) {
|
||||
// Bounds ** FIXME: shouldn't be hardcoded!
|
||||
this.mapwidth=800;
|
||||
this.mapheight=400;
|
||||
|
||||
// Initialise variables
|
||||
this.nodeuis={},
|
||||
this.wayuis={},
|
||||
this.div=document.getElementById(_divname);
|
||||
this.surface=Gfx.createSurface(_divname, this.mapwidth, this.mapheight);
|
||||
this.backdrop=this.surface.createRect( { x:0, y:0, width: this.mapwidth, height: this.mapheight }).setFill(new dojo.Color([255,255,245,1]));
|
||||
this.tilegroup=this.surface.createGroup();
|
||||
this.container=this.surface.createGroup();
|
||||
this.conn=_conn;
|
||||
this.scale=_scale;
|
||||
this.baselon=_lon;
|
||||
this.baselat=_lat;
|
||||
this.baselatp=this.lat2latp(_lat);
|
||||
this.setScaleFactor();
|
||||
this.updateCoordsFromViewportPosition();
|
||||
|
||||
// Initialise layers
|
||||
this.layers={};
|
||||
for (var l=this.minlayer; l<=this.maxlayer; l++) {
|
||||
var r=this.container.createGroup();
|
||||
this.layers[l]={
|
||||
root: r,
|
||||
fill: r.createGroup(),
|
||||
casing: r.createGroup(),
|
||||
stroke: r.createGroup(),
|
||||
text: r.createGroup(),
|
||||
hit: r.createGroup()
|
||||
};
|
||||
}
|
||||
|
||||
// Create group for elastic band
|
||||
this.elastic = this.container.createGroup();
|
||||
|
||||
// Make draggable
|
||||
this.backdrop.connect("onmousedown", lang.hitch(this,"startDrag"));
|
||||
this.tilegroup.connect("onmousedown", lang.hitch(this,"startDrag"));
|
||||
this.surface.connect("onclick", lang.hitch(this,"clickSurface"));
|
||||
this.surface.connect("onmousemove", lang.hitch(this,"processMove"));
|
||||
this.surface.connect("onmousedown", lang.hitch(this,"mouseEvent"));
|
||||
this.surface.connect("onmouseup", lang.hitch(this,"mouseEvent"));
|
||||
},
|
||||
|
||||
setController:function(_controller) {
|
||||
this.controller=_controller;
|
||||
},
|
||||
|
||||
// Supplementary method for gfx - moveToPosition
|
||||
// This should ideally be core Dojo stuff: see http://bugs.dojotoolkit.org/ticket/15296
|
||||
|
||||
moveToPosition:function(group,position) {
|
||||
var parent=group.getParent();
|
||||
if (!parent) { return; }
|
||||
this.moveChildToPosition(parent,group,position);
|
||||
if (position==group.rawNode.parentNode.childNodes.length) {
|
||||
group.rawNode.parentNode.appendChild(group.rawNode);
|
||||
} else {
|
||||
group.rawNode.parentNode.insertBefore(group.rawNode, group.rawNode.parentNode.childNodes[position]);
|
||||
}
|
||||
},
|
||||
|
||||
moveChildToPosition: function(parent,child,position) {
|
||||
for(var i = 0; i < parent.children.length; ++i){
|
||||
if(parent.children[i] == child){
|
||||
parent.children.splice(i, 1);
|
||||
parent.children.splice(position, 0, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Sprite and EntityUI handling
|
||||
|
||||
sublayer:function(layer,groupType,sublayer) {
|
||||
// Sublayers are only implemented for stroke and fill
|
||||
var collection=this.layers[layer][groupType];
|
||||
switch (groupType) {
|
||||
case 'casing':
|
||||
case 'text':
|
||||
case 'hit':
|
||||
return collection;
|
||||
}
|
||||
// Find correct sublayer, inserting if necessary
|
||||
var insertAt=collection.children.length;
|
||||
for (var i=0; i<collection.children.length; i++) {
|
||||
var sub=collection.children[i];
|
||||
if (sub.sublayer==sublayer) { return sub; }
|
||||
else if (sub.sublayer>sublayer) {
|
||||
sub=collection.createGroup();
|
||||
this.moveToPosition(sub,i);
|
||||
sub.sublayer=sublayer;
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
sub=collection.createGroup().moveToFront();
|
||||
sub.sublayer=sublayer;
|
||||
return sub;
|
||||
},
|
||||
|
||||
createUI:function(entity,stateClasses) {
|
||||
var id=entity.id;
|
||||
switch (entity.entityType) {
|
||||
case 'node':
|
||||
if (!this.nodeuis[id]) { this.nodeuis[id]=new iD.renderer.NodeUI(entity,this,stateClasses); }
|
||||
else { this.nodeuis[id].setStateClasses(stateClasses).redraw(); }
|
||||
return this.nodeuis[id];
|
||||
case 'way':
|
||||
if (!this.wayuis[id]) { this.wayuis[id]=new iD.renderer.WayUI(entity,this,stateClasses); }
|
||||
else { this.wayuis[id].setStateClasses(stateClasses).redraw(); }
|
||||
return this.wayuis[id];
|
||||
}
|
||||
},
|
||||
|
||||
getUI:function(entity) {
|
||||
switch (entity.entityType) {
|
||||
case 'node': return this.nodeuis[entity.id];
|
||||
case 'way': return this.wayuis[entity.id];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
refreshUI:function(entity) {
|
||||
switch (entity.entityType) {
|
||||
case 'node': if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].redraw(); } break;
|
||||
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].redraw(); } break;
|
||||
}
|
||||
},
|
||||
|
||||
deleteUI:function(entity) {
|
||||
switch (entity.entityType) {
|
||||
case 'node': if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].removeSprites(); delete this.nodeuis[entity.id]; } break;
|
||||
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].removeSprites(); delete this.wayuis[entity.id]; } break;
|
||||
}
|
||||
},
|
||||
|
||||
// Ask connection to load data
|
||||
download:function() {
|
||||
this.conn.loadFromAPI(this.edgel, this.edger, this.edget, this.edgeb);
|
||||
},
|
||||
|
||||
// Draw/refresh all EntityUIs within the bbox, and remove any others
|
||||
updateUIs:function(redraw,remove) {
|
||||
var m = this;
|
||||
var way, poi;
|
||||
var o = this.conn.getObjectsByBbox(this.edgel,this.edger,this.edget,this.edgeb);
|
||||
|
||||
array.forEach(o.waysInside, function(way) {
|
||||
if (!way.loaded) return;
|
||||
if (!m.wayuis[way.id]) { m.createUI(way); }
|
||||
else if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); }
|
||||
});
|
||||
|
||||
if (remove) {
|
||||
array.forEach(o.waysOutside, function(way) {
|
||||
if (m.wayuis[way.id]) { // && !m.wayuis[way.id].purgable
|
||||
if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); }
|
||||
} else { m.deleteUI(way); }
|
||||
});
|
||||
}
|
||||
|
||||
array.forEach(o.poisInside, function(poi) {
|
||||
if (!poi.loaded) return;
|
||||
if (!m.nodeuis[poi.id]) { m.createUI(poi); }
|
||||
else if (redraw) { m.nodeuis[poi.id].redraw(); }
|
||||
});
|
||||
|
||||
if (remove) {
|
||||
array.forEach(o.poisOutside, function(poi) {
|
||||
if (m.nodeuis[poi.id]) { // && !m.nodeuis[poi.id].purgable
|
||||
if (redraw) { m.nodeuis[poi.id].redraw(); }
|
||||
} else { m.deleteUI(poi); }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Zoom handling
|
||||
|
||||
zoomIn:function() {
|
||||
if (this.scale!=this.MAXSCALE) { this.changeScale(this.scale+1); }
|
||||
},
|
||||
|
||||
zoomOut:function() {
|
||||
if (this.scale!=this.MINSCALE) { this.changeScale(this.scale-1); }
|
||||
this.download();
|
||||
},
|
||||
|
||||
changeScale:function(_scale) {
|
||||
this.scale=_scale;
|
||||
this.setScaleFactor();
|
||||
this.blankTiles();
|
||||
this.updateCoordsFromLatLon(this.centrelat,this.centrelon); // recentre
|
||||
this.updateUIs(true,true);
|
||||
},
|
||||
|
||||
setScaleFactor:function() {
|
||||
this.scalefactor=this.MASTERSCALE/Math.pow(2,13-this.scale);
|
||||
},
|
||||
|
||||
// Elastic band redrawing
|
||||
|
||||
clearElastic:function() {
|
||||
this.elastic.clear();
|
||||
},
|
||||
|
||||
drawElastic:function(x1,y1,x2,y2) {
|
||||
this.elastic.clear();
|
||||
// **** Next line is SVG-specific
|
||||
this.elastic.rawNode.setAttribute("pointer-events","none");
|
||||
this.elastic.createPolyline( [{ x:x1, y:y1 }, { x:x2, y:y2 }] ).setStroke( {
|
||||
color: [0,0,0,1],
|
||||
style: 'Solid',
|
||||
width: 1 });
|
||||
},
|
||||
|
||||
// Tile handling
|
||||
// ** FIXME: needs to have configurable URLs
|
||||
// ** FIXME: needs Bing attribution/logo etc.
|
||||
// ** FIXME: needs to be nudgable
|
||||
|
||||
loadTiles:function() {
|
||||
var tile_l=this.lon2tile(this.edgel);
|
||||
var tile_r=this.lon2tile(this.edger);
|
||||
var tile_t=this.lat2tile(this.edget);
|
||||
var tile_b=this.lat2tile(this.edgeb);
|
||||
for (var x=tile_l; x<=tile_r; x++) {
|
||||
for (var y=tile_t; y<=tile_b; y++) {
|
||||
if (!this.getTile(this.scale,x,y)) { this.fetchTile(this.scale,x,y); }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fetchTile:function(z,x,y) {
|
||||
var t=this.tilegroup.createImage({
|
||||
x: this.lon2coord(this.tile2lon(x)),
|
||||
y: this.lat2coord(this.tile2lat(y)),
|
||||
width: 256, height: 256,
|
||||
src: this.tileURL(z,x,y)
|
||||
});
|
||||
this.assignTile(z,x,y,t);
|
||||
},
|
||||
|
||||
getTile:function(z,x,y) {
|
||||
if (this.tiles[z]==undefined) { return undefined; }
|
||||
if (this.tiles[z][x]==undefined) { return undefined; }
|
||||
return this.tiles[z][x][y];
|
||||
},
|
||||
|
||||
assignTile:function(z,x,y,t) {
|
||||
if (this.tiles[z]==undefined) { this.tiles[z]=[]; }
|
||||
if (this.tiles[z][x]==undefined) { this.tiles[z][x]=[]; }
|
||||
this.tiles[z][x][y]=t;
|
||||
},
|
||||
|
||||
tileURL:function(z,x,y) {
|
||||
var u='';
|
||||
for (var zoom=z; zoom>0; zoom--) {
|
||||
var byte=0;
|
||||
var mask=1<<(zoom-1);
|
||||
if ((x & mask)!=0) byte++;
|
||||
if ((y & mask)!=0) byte+=2;
|
||||
u=u+byte.toString();
|
||||
}
|
||||
return this.tilebaseURL.replace('$z',z).replace('$x',x).replace('$y',y).replace('$quadkey',u);
|
||||
},
|
||||
|
||||
blankTiles:function() {
|
||||
this.tilegroup.clear();
|
||||
this.tiles=[];
|
||||
},
|
||||
|
||||
// Co-ordinate management, dragging and redraw
|
||||
|
||||
startDrag:function(e) {
|
||||
var srcElement = (e.gfxTarget==this.backdrop) ? e.gfxTarget : e.gfxTarget.parent;
|
||||
Event.stop(e);
|
||||
this.dragging=true;
|
||||
this.dragged=false;
|
||||
this.dragx=this.dragy=NaN;
|
||||
this.startdragx=e.clientX;
|
||||
this.startdragy=e.clientY;
|
||||
this.dragconnect=srcElement.connect("onmouseup", lang.hitch(this,"endDrag"));
|
||||
},
|
||||
|
||||
endDrag:function(e) {
|
||||
Event.stop(e);
|
||||
dojo.disconnect(this.dragconnect);
|
||||
this.dragging=false;
|
||||
this.dragtime=e.timeStamp;
|
||||
this.updateCoordsFromViewportPosition();
|
||||
if (Math.abs(e.clientX-this.startdragx)<3 && Math.abs(e.clientY-this.startdragy)<3) { return; }
|
||||
this.download();
|
||||
},
|
||||
|
||||
processMove:function(e) {
|
||||
var x=e.clientX;
|
||||
var y=e.clientY;
|
||||
if (this.dragging) {
|
||||
if (this.dragx) {
|
||||
this.containerx+=(x-this.dragx);
|
||||
this.containery+=(y-this.dragy);
|
||||
this.updateOrigin();
|
||||
this.dragged=true;
|
||||
}
|
||||
this.dragx=x;
|
||||
this.dragy=y;
|
||||
} else {
|
||||
this.controller.entityMouseEvent(e,null);
|
||||
}
|
||||
},
|
||||
|
||||
// Tell Dojo to update the viewport origin
|
||||
updateOrigin:function() {
|
||||
this.container.setTransform([Matrix.translate(this.containerx,this.containery)]);
|
||||
this.tilegroup.setTransform([Matrix.translate(this.containerx,this.containery)]);
|
||||
},
|
||||
|
||||
mouseEvent:function(e) {
|
||||
// If the user mouses down within the fill of a shape, start the drag
|
||||
if (e.type=='mousedown') { this.startDrag(e); }
|
||||
// ** FIXME: we may want to reinstate this at some point...
|
||||
// this.controller.entityMouseEvent(e,null);
|
||||
},
|
||||
|
||||
// Update centre and bbox from the current viewport origin
|
||||
updateCoordsFromViewportPosition:function(e) {
|
||||
this.updateCoords(this.containerx,this.containery);
|
||||
},
|
||||
|
||||
// Update centre and bbox to a specified lat/lon
|
||||
updateCoordsFromLatLon:function(lat,lon) {
|
||||
this.updateCoords(-(this.lon2coord(lon)-this.mapwidth/2),
|
||||
-(this.lat2coord(lat)-this.mapheight/2));
|
||||
},
|
||||
|
||||
// Set centre and bbox, called from the above methods
|
||||
updateCoords:function(x,y) {
|
||||
this.containerx=x; this.containery=y; this.updateOrigin();
|
||||
this.centrelon=this.coord2lon(-x + this.mapwidth/2);
|
||||
this.centrelat=this.coord2lat(-y + this.mapheight/2);
|
||||
this.edget=this.coord2lat(-y);
|
||||
this.edgeb=this.coord2lat(-y + this.mapheight);
|
||||
this.edgel=this.coord2lon(-x);
|
||||
this.edger=this.coord2lon(-x + this.mapwidth);
|
||||
this.loadTiles();
|
||||
},
|
||||
|
||||
clickSurface:function(e) {
|
||||
if (this.dragged && e.timeStamp==this.dragtime) { return; }
|
||||
this.controller.entityMouseEvent(e,null);
|
||||
},
|
||||
|
||||
latp2coord:function(a) { return -(a-this.baselatp)*this.scalefactor; },
|
||||
coord2latp:function(a) { return a/-this.scalefactor+this.baselatp; },
|
||||
lon2coord:function(a) { return (a-this.baselon)*this.scalefactor; },
|
||||
coord2lon:function(a) { return a/this.scalefactor+this.baselon; },
|
||||
lat2latp:function(a) { return 180/Math.PI * Math.log(Math.tan(Math.PI/4+a*(Math.PI/180)/2)); },
|
||||
latp2lat:function(a) { return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); },
|
||||
lat2coord:function(a) { return -(this.lat2latp(a)-this.baselatp)*this.scalefactor; },
|
||||
coord2lat:function(a) { return this.latp2lat(a/-this.scalefactor+this.baselatp); },
|
||||
|
||||
lon2tile:function(a) { return (Math.floor((a+180)/360*Math.pow(2,this.scale))); },
|
||||
lat2tile:function(a) { return (Math.floor((1-Math.log(Math.tan(a*Math.PI/180) + 1/Math.cos(a*Math.PI/180))/Math.PI)/2 *Math.pow(2,this.scale))); },
|
||||
tile2lon:function(a) { return (a/Math.pow(2,this.scale)*360-180); },
|
||||
tile2lat:function(a) {
|
||||
var n=Math.PI-2*Math.PI*a/Math.pow(2,this.scale);
|
||||
return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
|
||||
},
|
||||
|
||||
// Turn event co-ordinates into map co-ordinates
|
||||
|
||||
mouseX:function(e) { return e.clientX - domGeom.getMarginBox(this.div).l - this.containerx; },
|
||||
mouseY:function(e) { return e.clientY - domGeom.getMarginBox(this.div).t - this.containery; },
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
79
js/iD/renderer/NodeUI.js
Executable file
79
js/iD/renderer/NodeUI.js
Executable file
@@ -0,0 +1,79 @@
|
||||
// iD/renderer/NodeUI.js
|
||||
// NodeUI classes for iD
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/array','dojox/gfx/_base','iD/renderer/EntityUI'],
|
||||
function(declare,lang,array,g){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// NodeUI class
|
||||
|
||||
declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
|
||||
constructor:function() {
|
||||
this.redraw();
|
||||
},
|
||||
getEnhancedTags:function() {
|
||||
var tags=this.inherited(arguments);
|
||||
if (!this.entity.hasParentWays()) { tags[':poi']='yes'; }
|
||||
// add junction and dupe
|
||||
return tags;
|
||||
},
|
||||
redraw:function() {
|
||||
var node=this.entity;
|
||||
this.removeSprites();
|
||||
|
||||
// Tags, position and styleList
|
||||
var x=this.map.lon2coord(this.entity.lon);
|
||||
var y=this.map.latp2coord(this.entity.latp);
|
||||
var tags=this.getEnhancedTags();
|
||||
this.refreshStyleList(tags);
|
||||
|
||||
// Iterate through each subpart, drawing any styles on that layer
|
||||
var drawn=false;
|
||||
var s,p,t,w,h;
|
||||
for (i=0; i<this.styleList.subparts.length; i++) {
|
||||
var subpart=this.styleList.subparts[i];
|
||||
p = this.styleList.pointStyles[subpart];
|
||||
if (!p || !p.drawn()) { continue; }
|
||||
s = this.styleList.shapeStyles[subpart];
|
||||
t = this.styleList.textStyles[subpart];
|
||||
w = p.icon_width ? p.icon_width : 16;
|
||||
h = p.icon_height ? p.icon_height: w;
|
||||
|
||||
// Draw icon
|
||||
var shape;
|
||||
switch (p.icon_image) {
|
||||
case 'square': shape=this.targetGroup('stroke',p.sublayer).createRect({ x:x-w/2, y:y-h/2, width:w, height:h }); break;
|
||||
case 'circle': shape=this.targetGroup('stroke',p.sublayer).createCircle({ cx:x, cy:y, r:w }); break;
|
||||
default: shape=this.targetGroup('stroke',p.sublayer).createImage({ width:w, height:h, x: x-w/2, y: y-h/2, src:p.icon_image }); break;
|
||||
}
|
||||
switch (p.icon_image) {
|
||||
case 'square':
|
||||
case 'circle': shape.setStroke(s.shapeStrokeStyler()).setFill(s.shapeFillStyler()); break;
|
||||
}
|
||||
this.recordSprite(shape);
|
||||
|
||||
// Add text label
|
||||
|
||||
// Add hit-zone
|
||||
var hit;
|
||||
switch (p.icon_image) {
|
||||
case 'circle': hit=this.targetGroup('hit').createCircle({ cx:x, cy:y, r:w }); break;
|
||||
default: hit=this.targetGroup('hit').createRect({ x:x-w/2, y:y-h/2, width:w, height: h}); break;
|
||||
}
|
||||
hit.setFill([0,1,0,0]).setStroke( { width:2, color:[0,0,0,0] } );
|
||||
this.recordSprite(hit);
|
||||
hit.source=this;
|
||||
hit.connect("onclick" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmousedown" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseup" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseenter", lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseleave", lang.hitch(this,this.entityMouseEvent));
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
119
js/iD/renderer/WayUI.js
Executable file
119
js/iD/renderer/WayUI.js
Executable file
@@ -0,0 +1,119 @@
|
||||
// iD/renderer/WayUI.js
|
||||
// WayUI classes for iD
|
||||
// multipolygon support - http://mail.dojotoolkit.org/pipermail/dojo-interest/2011-January/052042.html
|
||||
// support 'interactive'
|
||||
// line decoration, dots etc.
|
||||
// fill images
|
||||
// opacity
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','iD/renderer/EntityUI'], function(declare,lang){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// WayUI class
|
||||
|
||||
declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
|
||||
constructor:function() {
|
||||
this.redraw();
|
||||
},
|
||||
getEnhancedTags:function() {
|
||||
var tags=this.inherited(arguments);
|
||||
if (this.entity.isClosed()) { tags[':area']='yes'; }
|
||||
return tags;
|
||||
},
|
||||
recalculate:function() {
|
||||
// ** FIXME: todo
|
||||
},
|
||||
redraw:function() {
|
||||
var way=this.entity;
|
||||
var maxwidth=4;
|
||||
var i;
|
||||
this.removeSprites();
|
||||
if (way.length()==0) { return; }
|
||||
|
||||
// Create tags and calculate styleList
|
||||
var tags=this.getEnhancedTags();
|
||||
this.refreshStyleList(tags);
|
||||
|
||||
// List of co-ordinates
|
||||
var coords=[];
|
||||
for (i=0; i<way.nodes.length; i++) {
|
||||
var node=way.nodes[i];
|
||||
coords.push( { x: this.map.lon2coord(node.lon), y: this.map.latp2coord(node.latp) } );
|
||||
}
|
||||
|
||||
// Iterate through each subpart, drawing any styles on that layer
|
||||
var drawn=false;
|
||||
for (i=0; i<this.styleList.subparts.length; i++) {
|
||||
var subpart=this.styleList.subparts[i];
|
||||
if (this.styleList.shapeStyles[subpart]) {
|
||||
var s=this.styleList.shapeStyles[subpart];
|
||||
|
||||
// Stroke
|
||||
if (s.width) {
|
||||
this.recordSprite(this.targetGroup('stroke',s.sublayer).createPolyline(coords).setStroke(s.strokeStyler()));
|
||||
maxwidth=Math.max(maxwidth,s.width);
|
||||
drawn=true;
|
||||
}
|
||||
|
||||
// Fill
|
||||
if (!isNaN(s.fill_color)) {
|
||||
this.recordSprite(this.targetGroup('fill',s.sublayer).createPolyline(coords).setFill(s.fillStyler()));
|
||||
drawn=true;
|
||||
}
|
||||
|
||||
// Casing
|
||||
if (s.casing_width) {
|
||||
this.recordSprite(this.targetGroup('casing').createPolyline(coords).setStroke(s.casingStyler()));
|
||||
maxwidth=Math.max(maxwidth,s.width+s.casing_width*2);
|
||||
drawn=true;
|
||||
}
|
||||
}
|
||||
|
||||
// Text label on path
|
||||
if (this.styleList.textStyles[subpart]) {
|
||||
var t=this.styleList.textStyles[subpart];
|
||||
if (t.text && tags[t.text]) {
|
||||
var tp=this.recordSprite(this.targetGroup('text')
|
||||
.createTextPath(t.textStyler(tags[t.text]))
|
||||
.setFont(t.fontStyler())
|
||||
.setFill(t.fillStyler())
|
||||
.moveTo(coords[0].x,coords[0].y));
|
||||
for (var j=1; j<coords.length; j++) {
|
||||
tp.lineTo(coords[j].x,coords[j].y);
|
||||
}
|
||||
// *** this next line is SVG-specific
|
||||
tp.rawNode.setAttribute("pointer-events","none");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add hitzone sprite
|
||||
if (drawn) {
|
||||
var hit=this.recordSprite(this.targetGroup('hit').createPolyline(coords).setStroke( { width:maxwidth+8, color: [0,0,0,0] } ));
|
||||
hit.source=this;
|
||||
hit.connect("onclick" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmousedown" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseup" , lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseenter", lang.hitch(this,this.entityMouseEvent));
|
||||
hit.connect("onmouseleave", lang.hitch(this,this.entityMouseEvent));
|
||||
}
|
||||
// Draw nodes
|
||||
for (i=0; i<way.length(); i++) {
|
||||
var node=way.nodes[i];
|
||||
var sc=[];
|
||||
if (tags[':shownodes']) { sc.push('selectedway'); }
|
||||
if (tags[':shownodeshover']) { sc.push('hoverway'); }
|
||||
if (node.parentWays().length>1) { sc.push('junction'); }
|
||||
this.map.createUI(node,sc);
|
||||
}
|
||||
},
|
||||
|
||||
entityMouseEvent:function(event) {
|
||||
this.inherited(arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
44
js/iD/styleparser/Condition.js
Executable file
44
js/iD/styleparser/Condition.js
Executable file
@@ -0,0 +1,44 @@
|
||||
// iD/styleparser/Condition.js
|
||||
|
||||
define(['dojo/_base/declare'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Condition base class
|
||||
|
||||
declare("iD.styleparser.Condition", null, {
|
||||
type: '', // eq/ne/regex etc.
|
||||
params: [], // what to test against
|
||||
|
||||
constructor:function(_type) {
|
||||
this.type =_type;
|
||||
this.params=Array.prototype.slice.call(arguments,1);
|
||||
},
|
||||
|
||||
test:function(tags) {
|
||||
var p=this.params;
|
||||
switch (this.type) {
|
||||
case 'eq': return (tags[p[0]]==p[1]); break;
|
||||
case 'ne': return (tags[p[0]]!=p[1]); break;
|
||||
case 'regex': var r=new RegExp(p[1],"i");
|
||||
return (r.test(tags[p[0]])); break;
|
||||
case 'true': return (tags[p[0]]=='true' || tags[p[0]]=='yes' || tags[p[0]]=='1'); break;
|
||||
case 'false': return (tags[p[0]]=='false' || tags[p[0]]=='no' || tags[p[0]]=='0'); break;
|
||||
case 'set': return (tags[p[0]]!=undefined && tags[p[0]]!=''); break;
|
||||
case 'unset': return (tags[p[0]]==undefined || tags[p[0]]==''); break;
|
||||
case '<': return (Number(tags[p[0]])< Number(p[1])); break;
|
||||
case '<=': return (Number(tags[p[0]])<=Number(p[1])); break;
|
||||
case '>': return (Number(tags[p[0]])> Number(p[1])); break;
|
||||
case '>=': return (Number(tags[p[0]])>=Number(p[1])); break;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
return "["+this.type+": "+this.params+"]";
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
54
js/iD/styleparser/Rule.js
Executable file
54
js/iD/styleparser/Rule.js
Executable file
@@ -0,0 +1,54 @@
|
||||
// iD/styleparser/Rule.js
|
||||
|
||||
/** A MapCSS selector. Contains a list of Conditions; the entity type to which the selector applies;
|
||||
and the zoom levels at which it is true. way[waterway=river][boat=yes] would be parsed into one Rule.
|
||||
The selectors and declaration together form a StyleChooser. */
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array'], function(declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Rule base class
|
||||
|
||||
declare("iD.styleparser.Rule", null, {
|
||||
|
||||
conditions: [], // the Conditions to be evaluated for the Rule to be fulfilled
|
||||
isAnd: true, // do all Conditions need to be true for the Rule to be fulfilled? (Always =true for MapCSS)
|
||||
minZoom: 0, // minimum zoom level at which the Rule is fulfilled
|
||||
maxZoom: 255, // maximum zoom level at which the Rule is fulfilled
|
||||
subject: '', // entity type to which the Rule applies: 'way', 'node', 'relation', 'area' (closed way) or 'line' (unclosed way)
|
||||
|
||||
constructor:function(_subject) {
|
||||
this.subject=_subject;
|
||||
this.conditions=[];
|
||||
},
|
||||
|
||||
addCondition:function(_condition) {
|
||||
this.conditions.push(_condition);
|
||||
},
|
||||
|
||||
/** Evaluate the Rule on the given entity, tags and zoom level.
|
||||
Return true if the Rule passes, false if the conditions aren't fulfilled. */
|
||||
|
||||
test:function(entity,tags,zoom) {
|
||||
if (this.subject!='' && !entity.isType(this.subject)) { return false; }
|
||||
if (zoom<this.minZoom || zoom>this.maxZoom) { return false; }
|
||||
|
||||
var v=true; var i=0; var isAnd=this.isAnd;
|
||||
array.forEach(this.conditions, function(condition) {
|
||||
var r=condition.test(tags);
|
||||
if (i==0) { v=r; }
|
||||
else if (isAnd) { v=v && r; }
|
||||
else { v = v || r; }
|
||||
i++;
|
||||
});
|
||||
return v;
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
return this.subject+" z"+this.minZoom+"-"+this.maxZoom+": "+this.conditions;
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
82
js/iD/styleparser/RuleChain.js
Executable file
82
js/iD/styleparser/RuleChain.js
Executable file
@@ -0,0 +1,82 @@
|
||||
// iD/styleparser/RuleChain.js
|
||||
|
||||
define(['dojo/_base/declare','iD/styleparser/Rule'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// RuleChain base class
|
||||
// In contrast to Halcyon, note that length() is a function, not a getter property
|
||||
|
||||
/** A descendant list of MapCSS selectors (Rules).
|
||||
|
||||
For example,
|
||||
relation[type=route] way[highway=primary]
|
||||
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
|
||||
first Rule second Rule
|
||||
|------------|---------|
|
||||
|
|
||||
one RuleChain
|
||||
|
||||
*/
|
||||
|
||||
declare("iD.styleparser.RuleChain", null, {
|
||||
|
||||
rules:[], // list of Rules
|
||||
subpart: 'default', // subpart name, as in way[highway=primary]::centreline
|
||||
|
||||
constructor:function() {
|
||||
this.rules=[];
|
||||
},
|
||||
|
||||
// Functions to define the RuleChain
|
||||
|
||||
addRule:function(_subject) {
|
||||
this.rules.push(new iD.styleparser.Rule(_subject));
|
||||
},
|
||||
|
||||
addConditionToLast:function(_condition) {
|
||||
this.rules[this.rules.length-1].addCondition(_condition);
|
||||
},
|
||||
|
||||
addZoomToLast:function(z1,z2) {
|
||||
this.rules[this.rules.length-1].minZoom=z1;
|
||||
this.rules[this.rules.length-1].maxZoom=z2;
|
||||
},
|
||||
|
||||
|
||||
length:function() {
|
||||
return this.rules.length;
|
||||
},
|
||||
|
||||
setSubpart:function(_subpart) {
|
||||
this.subpart = _subpart=='' ? 'default' : _subpart;
|
||||
},
|
||||
|
||||
// Test a ruleChain
|
||||
// - run a set of tests in the chain
|
||||
// works backwards from at position "pos" in array, or -1 for the last
|
||||
// separate tags object is required in case they've been dynamically retagged
|
||||
// - if they fail, return false
|
||||
// - if they succeed, and it's the last in the chain, return happily
|
||||
// - if they succeed, and there's more in the chain, rerun this for each parent until success
|
||||
|
||||
test:function(pos, entity, tags, zoom) {
|
||||
if (this.rules.length==0) { return false; }
|
||||
if (pos==-1) { pos=this.rules.length-1; }
|
||||
|
||||
var r=this.rules[pos];
|
||||
if (!r.test(entity, tags, zoom)) { return false; }
|
||||
if (pos==0) { return true; }
|
||||
|
||||
var o=entity.parentObjects();
|
||||
for (var i=0; i<o.length; i++) {
|
||||
var p=o[i];
|
||||
if (this.test(pos-1, p, p.getTagsHash(), zoom)) { return true; }
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
456
js/iD/styleparser/RuleSet.js
Executable file
456
js/iD/styleparser/RuleSet.js
Executable file
@@ -0,0 +1,456 @@
|
||||
// iD/styleparser/RuleSet.js
|
||||
|
||||
define(['dojo/_base/xhr','dojo/_base/lang','dojo/_base/declare','dojo/_base/array','iD/styleparser/Style','iD/styleparser/StyleChooser','iD/styleparser/Condition','iD/styleparser/StyleList'], function(xhr,lang,declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// RuleSet base class
|
||||
// needs to cope with nested CSS files
|
||||
// evals not done
|
||||
// doesn't do untagged nodes optimisation
|
||||
|
||||
declare("iD.styleparser.RuleSet", null, {
|
||||
|
||||
choosers: [], // list of StyleChoosers
|
||||
callback: null,
|
||||
|
||||
constructor:function() {
|
||||
this.choosers=[];
|
||||
},
|
||||
|
||||
registerCallback:function(_callback) {
|
||||
this.callback=_callback;
|
||||
},
|
||||
|
||||
// Find the styles for a given entity
|
||||
|
||||
getStyles:function(entity, tags, zoom) {
|
||||
var sl=new iD.styleparser.StyleList();
|
||||
for (var i in this.choosers) {
|
||||
this.choosers[i].updateStyles(entity, tags, sl, zoom);
|
||||
}
|
||||
return sl;
|
||||
},
|
||||
|
||||
// Load from .mapcss file
|
||||
|
||||
loadFromCSS:function(url) {
|
||||
xhr.get({ url: url, load: lang.hitch(this, "parseCSS") });
|
||||
},
|
||||
|
||||
parseCSS:function(css) {
|
||||
var previous=0; // what was the previous CSS word?
|
||||
var sc=new iD.styleparser.StyleChooser(); // currently being assembled
|
||||
this.choosers=[];
|
||||
css=css.replace(/[\r\n]/g,""); // strip linebreaks because JavaScript doesn't have the /s modifier
|
||||
|
||||
var o={};
|
||||
while (css.length>0) {
|
||||
|
||||
// CSS comment
|
||||
if ((o=this.COMMENT.exec(css))) {
|
||||
css=css.replace(this.COMMENT,'');
|
||||
|
||||
// Whitespace (probably only at beginning of file)
|
||||
} else if ((o=this.WHITESPACE.exec(css))) {
|
||||
css=css.replace(this.WHITESPACE,'');
|
||||
|
||||
// Class - .motorway, .builtup, :hover
|
||||
} else if ((o=this.CLASS.exec(css))) {
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
|
||||
css=css.replace(this.CLASS,'');
|
||||
sc.currentChain().addConditionToLast(new iD.styleparser.Condition('set',o[1]));
|
||||
previous=this.oCONDITION;
|
||||
|
||||
// Not class - !.motorway, !.builtup, !:hover
|
||||
} else if ((o=this.NOT_CLASS.exec(css))) {
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
|
||||
css=css.replace(this.NOT_CLASS,'');
|
||||
sc.currentChain().addConditionToLast(new iD.styleparser.Condition('unset',o[1]));
|
||||
previous=this.oCONDITION;
|
||||
|
||||
// Zoom
|
||||
} else if ((o=this.ZOOM.exec(css))) {
|
||||
if (previous!=this.oOBJECT && previous!=this.oCONDITION) { sc.currentChain().addRule(); }
|
||||
|
||||
css=css.replace(this.ZOOM,'');
|
||||
var z=parseZoom(o[1]);
|
||||
sc.currentChain().addZoomToLast(z[0],z[1]);
|
||||
sc.zoomSpecific=true;
|
||||
previous=this.oZOOM;
|
||||
|
||||
// Grouping - just a comma
|
||||
} else if ((o=this.GROUP.exec(css))) {
|
||||
css=css.replace(this.GROUP,'');
|
||||
sc.newRuleChain();
|
||||
previous=this.oGROUP;
|
||||
|
||||
// Condition - [highway=primary]
|
||||
} else if ((o=this.CONDITION.exec(css))) {
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
if (previous!=this.oOBJECT && previous!=this.oZOOM && previous!=this.oCONDITION) { sc.currentChain().addRule(); }
|
||||
css=css.replace(this.CONDITION,'');
|
||||
sc.currentChain().addConditionToLast(this.parseCondition(o[1]));
|
||||
previous=this.oCONDITION;
|
||||
|
||||
// Object - way, node, relation
|
||||
} else if ((o=this.OBJECT.exec(css))) {
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
|
||||
css=css.replace(this.OBJECT,'');
|
||||
sc.currentChain().addRule(o[1]);
|
||||
previous=this.oOBJECT;
|
||||
|
||||
// Subpart - ::centreline
|
||||
} else if ((o=this.SUBPART.exec(css))) {
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
css=css.replace(this.SUBPART,'');
|
||||
sc.currentChain().setSubpart(o[1]);
|
||||
previous=this.oSUBPART;
|
||||
|
||||
// Declaration - {...}
|
||||
} else if ((o=this.DECLARATION.exec(css))) {
|
||||
css=css.replace(this.DECLARATION,'');
|
||||
sc.addStyles(this.parseDeclaration(o[1]));
|
||||
previous=this.oDECLARATION;
|
||||
|
||||
// Unknown pattern
|
||||
} else if ((o=this.UNKNOWN.exec(css))) {
|
||||
css=css.replace(this.UNKNOWN,'');
|
||||
console.log("unknown: "+o[1]);
|
||||
|
||||
} else {
|
||||
console.log("choked on "+css);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (previous==this.oDECLARATION) { this.saveChooser(sc); sc=new iD.styleparser.StyleChooser(); }
|
||||
if (this.callback) { this.callback(); }
|
||||
},
|
||||
|
||||
saveChooser:function(sc) {
|
||||
this.choosers.push(sc);
|
||||
},
|
||||
|
||||
parseDeclaration:function(s) {
|
||||
var styles=[];
|
||||
var t={};
|
||||
var o={};
|
||||
var k, v;
|
||||
|
||||
// Create styles
|
||||
var ss=new iD.styleparser.ShapeStyle() ;
|
||||
var ps=new iD.styleparser.PointStyle() ;
|
||||
var ts=new iD.styleparser.TextStyle() ;
|
||||
var hs=new iD.styleparser.ShieldStyle();
|
||||
var xs=new iD.styleparser.InstructionStyle();
|
||||
|
||||
var r=s.split(';');
|
||||
var isEval={};
|
||||
for (var i in r) {
|
||||
var a=r[i];
|
||||
if ((o=this.ASSIGNMENT_EVAL.exec(a))) { k=o[1].replace(this.DASH,'_'); t[k]=o[2]; isEval[k]=true; }
|
||||
else if ((o=this.ASSIGNMENT.exec(a))) { k=o[1].replace(this.DASH,'_'); t[k]=o[2]; }
|
||||
else if ((o=this.SET_TAG_EVAL.exec(a))) { } // xs.addSetTag(o[1],this.saveEval(o[2]));
|
||||
else if ((o=this.SET_TAG.exec(a))) { xs.addSetTag(o[1],o[2]); }
|
||||
else if ((o=this.SET_TAG_TRUE.exec(a))) { xs.addSetTag(o[1],true); }
|
||||
else if ((o=this.EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); }
|
||||
}
|
||||
|
||||
// Find sublayer
|
||||
var sub=5;
|
||||
if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; }
|
||||
ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub;
|
||||
xs.sublayer=10;
|
||||
|
||||
// Find "interactive" property - it's true unless explicitly set false
|
||||
var inter=true;
|
||||
if (t['interactive']) { inter=t['interactive'].match(this.FALSE) ? false : true; delete t['interactive']; }
|
||||
ss.interactive=ps.interactive=ts.interactive=hs.interactive=xs.interactive=inter;
|
||||
|
||||
// Munge special values
|
||||
// (we should stop doing this and do it in the style instead)
|
||||
if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(this.BOLD ) ? true : false; delete t['font_weight']; }
|
||||
if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(this.ITALIC) ? true : false; delete t['font_style']; }
|
||||
if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(this.UNDERLINE) ? true : false; delete t['text_decoration']; }
|
||||
if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(this.CENTER) ? true : false; delete t['text_position']; }
|
||||
if (t['text_transform']) {
|
||||
if (t['text_transform'].match(this.CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
|
||||
delete t['text_transform'];
|
||||
}
|
||||
|
||||
// Assign each property to the appropriate style
|
||||
for (a in t) {
|
||||
// Parse properties
|
||||
// ** also do units, e.g. px/pt/m
|
||||
if (a.match(this.COLOR)) { v = this.parseCSSColor(t[a]); }
|
||||
else { v = t[a]; }
|
||||
|
||||
// Set in styles
|
||||
if (ss.has(a)) { ss.setPropertyFromString(a,v,isEval[a]); }
|
||||
else if (ps.has(a)) { ps.setPropertyFromString(a,v,isEval[a]); }
|
||||
else if (ts.has(a)) { ts.setPropertyFromString(a,v,isEval[a]); }
|
||||
else if (hs.has(a)) { hs.setPropertyFromString(a,v,isEval[a]); }
|
||||
else { console.log(a+" not found"); }
|
||||
}
|
||||
|
||||
// Add each style to list
|
||||
if (ss.edited) { styles.push(ss); }
|
||||
if (ps.edited) { styles.push(ps); }
|
||||
if (ts.edited) { styles.push(ts); }
|
||||
if (hs.edited) { styles.push(hs); }
|
||||
if (xs.edited) { styles.push(xs); }
|
||||
return styles;
|
||||
},
|
||||
|
||||
parseZoom:function(s) {
|
||||
var o={};
|
||||
if ((o=this.ZOOM_MINMAX.exec(s))) { return [o[1],o[2]]; }
|
||||
else if ((o=this.ZOOM_MIN.exec(s) )) { return [o[1],maxscale]; }
|
||||
else if ((o=this.ZOOM_MAX.exec(s) )) { return [minscale,o[1]]; }
|
||||
else if ((o=this.ZOOM_SINGLE.exec(s))) { return [o[1],o[1]]; }
|
||||
return null;
|
||||
},
|
||||
|
||||
parseCondition:function(s) {
|
||||
var o={};
|
||||
if ((o=this.CONDITION_TRUE.exec(s))) { return new iD.styleparser.Condition('true' ,o[1]); }
|
||||
else if ((o=this.CONDITION_FALSE.exec(s))) { return new iD.styleparser.Condition('false',o[1]); }
|
||||
else if ((o=this.CONDITION_SET.exec(s))) { return new iD.styleparser.Condition('set' ,o[1]); }
|
||||
else if ((o=this.CONDITION_UNSET.exec(s))) { return new iD.styleparser.Condition('unset',o[1]); }
|
||||
else if ((o=this.CONDITION_NE.exec(s))) { return new iD.styleparser.Condition('ne' ,o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_GT.exec(s))) { return new iD.styleparser.Condition('>' ,o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_GE.exec(s))) { return new iD.styleparser.Condition('>=' ,o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_LT.exec(s))) { return new iD.styleparser.Condition('<' ,o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_LE.exec(s))) { return new iD.styleparser.Condition('<=' ,o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_REGEX.exec(s))) { return new iD.styleparser.Condition('regex',o[1],o[2]); }
|
||||
else if ((o=this.CONDITION_EQ.exec(s))) { return new iD.styleparser.Condition('eq' ,o[1],o[2]); }
|
||||
return null;
|
||||
},
|
||||
|
||||
parseCSSColor:function(colorStr) {
|
||||
colorStr = colorStr.toLowerCase();
|
||||
if (this.CSSCOLORS[colorStr]) {
|
||||
return this.CSSCOLORS[colorStr];
|
||||
} else {
|
||||
var match = this.HEX.exec(colorStr);
|
||||
if ( match ) {
|
||||
if ( match[1].length == 3) {
|
||||
// repeat digits. #abc => 0xaabbcc
|
||||
return Number("0x"+match[1].charAt(0)+match[1].charAt(0)+
|
||||
match[1].charAt(1)+match[1].charAt(1)+
|
||||
match[1].charAt(2)+match[1].charAt(2));
|
||||
} else if ( match[1].length == 6) {
|
||||
return Number("0x"+match[1]);
|
||||
} else {
|
||||
return Number("0x000000"); //as good as any
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
// Regular expression tests and other constants
|
||||
|
||||
WHITESPACE :/^\s+/,
|
||||
COMMENT :/^\/\*.+?\*\/\s*/,
|
||||
CLASS :/^([\.:]\w+)\s*/,
|
||||
NOT_CLASS :/^!([\.:]\w+)\s*/,
|
||||
ZOOM :/^\|\s*z([\d\-]+)\s*/i,
|
||||
GROUP :/^,\s*/i,
|
||||
CONDITION :/^\[(.+?)\]\s*/,
|
||||
OBJECT :/^(\w+)\s*/,
|
||||
DECLARATION :/^\{(.+?)\}\s*/,
|
||||
SUBPART :/^::(\w+)\s*/,
|
||||
UNKNOWN :/^(\S+)\s*/,
|
||||
|
||||
ZOOM_MINMAX :/^(\d+)\-(\d+)$/,
|
||||
ZOOM_MIN :/^(\d+)\-$/,
|
||||
ZOOM_MAX :/^\-(\d+)$/,
|
||||
ZOOM_SINGLE :/^(\d+)$/,
|
||||
|
||||
CONDITION_TRUE :/^\s*([:\w]+)\s*=\s*yes\s*$/i,
|
||||
CONDITION_FALSE :/^\s*([:\w]+)\s*=\s*no\s*$/i,
|
||||
CONDITION_SET :/^\s*([:\w]+)\s*$/,
|
||||
CONDITION_UNSET :/^\s*!([:\w]+)\s*$/,
|
||||
CONDITION_EQ :/^\s*([:\w]+)\s*=\s*(.+)\s*$/,
|
||||
CONDITION_NE :/^\s*([:\w]+)\s*!=\s*(.+)\s*$/,
|
||||
CONDITION_GT :/^\s*([:\w]+)\s*>\s*(.+)\s*$/,
|
||||
CONDITION_GE :/^\s*([:\w]+)\s*>=\s*(.+)\s*$/,
|
||||
CONDITION_LT :/^\s*([:\w]+)\s*<\s*(.+)\s*$/,
|
||||
CONDITION_LE :/^\s*([:\w]+)\s*<=\s*(.+)\s*$/,
|
||||
CONDITION_REGEX :/^\s*([:\w]+)\s*=~\/\s*(.+)\/\s*$/,
|
||||
|
||||
ASSIGNMENT_EVAL :/^\s*(\S+)\s*\:\s*eval\s*\(\s*'(.+?)'\s*\)\s*$/i,
|
||||
ASSIGNMENT :/^\s*(\S+)\s*\:\s*(.+?)\s*$/,
|
||||
SET_TAG_EVAL :/^\s*set\s+(\S+)\s*=\s*eval\s*\(\s*'(.+?)'\s*\)\s*$/i,
|
||||
SET_TAG :/^\s*set\s+(\S+)\s*=\s*(.+?)\s*$/i,
|
||||
SET_TAG_TRUE :/^\s*set\s+(\S+)\s*$/i,
|
||||
EXIT :/^\s*exit\s*$/i,
|
||||
|
||||
oZOOM: 2,
|
||||
oGROUP: 3,
|
||||
oCONDITION: 4,
|
||||
oOBJECT: 5,
|
||||
oDECLARATION: 6,
|
||||
oSUBPART: 7,
|
||||
|
||||
DASH: /\-/g,
|
||||
COLOR: /color$/,
|
||||
BOLD: /^bold$/i,
|
||||
ITALIC: /^italic|oblique$/i,
|
||||
UNDERLINE: /^underline$/i,
|
||||
CAPS: /^uppercase$/i,
|
||||
CENTER: /^center$/i,
|
||||
FALSE: /^(no|false|0)$/i,
|
||||
|
||||
HEX: /^#([0-9a-f]+)$/i,
|
||||
|
||||
CSSCOLORS: {
|
||||
aliceblue:0xf0f8ff,
|
||||
antiquewhite:0xfaebd7,
|
||||
aqua:0x00ffff,
|
||||
aquamarine:0x7fffd4,
|
||||
azure:0xf0ffff,
|
||||
beige:0xf5f5dc,
|
||||
bisque:0xffe4c4,
|
||||
black:0x000000,
|
||||
blanchedalmond:0xffebcd,
|
||||
blue:0x0000ff,
|
||||
blueviolet:0x8a2be2,
|
||||
brown:0xa52a2a,
|
||||
burlywood:0xdeb887,
|
||||
cadetblue:0x5f9ea0,
|
||||
chartreuse:0x7fff00,
|
||||
chocolate:0xd2691e,
|
||||
coral:0xff7f50,
|
||||
cornflowerblue:0x6495ed,
|
||||
cornsilk:0xfff8dc,
|
||||
crimson:0xdc143c,
|
||||
cyan:0x00ffff,
|
||||
darkblue:0x00008b,
|
||||
darkcyan:0x008b8b,
|
||||
darkgoldenrod:0xb8860b,
|
||||
darkgray:0xa9a9a9,
|
||||
darkgreen:0x006400,
|
||||
darkkhaki:0xbdb76b,
|
||||
darkmagenta:0x8b008b,
|
||||
darkolivegreen:0x556b2f,
|
||||
darkorange:0xff8c00,
|
||||
darkorchid:0x9932cc,
|
||||
darkred:0x8b0000,
|
||||
darksalmon:0xe9967a,
|
||||
darkseagreen:0x8fbc8f,
|
||||
darkslateblue:0x483d8b,
|
||||
darkslategray:0x2f4f4f,
|
||||
darkturquoise:0x00ced1,
|
||||
darkviolet:0x9400d3,
|
||||
deeppink:0xff1493,
|
||||
deepskyblue:0x00bfff,
|
||||
dimgray:0x696969,
|
||||
dodgerblue:0x1e90ff,
|
||||
firebrick:0xb22222,
|
||||
floralwhite:0xfffaf0,
|
||||
forestgreen:0x228b22,
|
||||
fuchsia:0xff00ff,
|
||||
gainsboro:0xdcdcdc,
|
||||
ghostwhite:0xf8f8ff,
|
||||
gold:0xffd700,
|
||||
goldenrod:0xdaa520,
|
||||
gray:0x808080,
|
||||
green:0x008000,
|
||||
greenyellow:0xadff2f,
|
||||
honeydew:0xf0fff0,
|
||||
hotpink:0xff69b4,
|
||||
indianred:0xcd5c5c,
|
||||
indigo:0x4b0082,
|
||||
ivory:0xfffff0,
|
||||
khaki:0xf0e68c,
|
||||
lavender:0xe6e6fa,
|
||||
lavenderblush:0xfff0f5,
|
||||
lawngreen:0x7cfc00,
|
||||
lemonchiffon:0xfffacd,
|
||||
lightblue:0xadd8e6,
|
||||
lightcoral:0xf08080,
|
||||
lightcyan:0xe0ffff,
|
||||
lightgoldenrodyellow:0xfafad2,
|
||||
lightgrey:0xd3d3d3,
|
||||
lightgreen:0x90ee90,
|
||||
lightpink:0xffb6c1,
|
||||
lightsalmon:0xffa07a,
|
||||
lightseagreen:0x20b2aa,
|
||||
lightskyblue:0x87cefa,
|
||||
lightslategray:0x778899,
|
||||
lightsteelblue:0xb0c4de,
|
||||
lightyellow:0xffffe0,
|
||||
lime:0x00ff00,
|
||||
limegreen:0x32cd32,
|
||||
linen:0xfaf0e6,
|
||||
magenta:0xff00ff,
|
||||
maroon:0x800000,
|
||||
mediumaquamarine:0x66cdaa,
|
||||
mediumblue:0x0000cd,
|
||||
mediumorchid:0xba55d3,
|
||||
mediumpurple:0x9370d8,
|
||||
mediumseagreen:0x3cb371,
|
||||
mediumslateblue:0x7b68ee,
|
||||
mediumspringgreen:0x00fa9a,
|
||||
mediumturquoise:0x48d1cc,
|
||||
mediumvioletred:0xc71585,
|
||||
midnightblue:0x191970,
|
||||
mintcream:0xf5fffa,
|
||||
mistyrose:0xffe4e1,
|
||||
moccasin:0xffe4b5,
|
||||
navajowhite:0xffdead,
|
||||
navy:0x000080,
|
||||
oldlace:0xfdf5e6,
|
||||
olive:0x808000,
|
||||
olivedrab:0x6b8e23,
|
||||
orange:0xffa500,
|
||||
orangered:0xff4500,
|
||||
orchid:0xda70d6,
|
||||
palegoldenrod:0xeee8aa,
|
||||
palegreen:0x98fb98,
|
||||
paleturquoise:0xafeeee,
|
||||
palevioletred:0xd87093,
|
||||
papayawhip:0xffefd5,
|
||||
peachpuff:0xffdab9,
|
||||
peru:0xcd853f,
|
||||
pink:0xffc0cb,
|
||||
plum:0xdda0dd,
|
||||
powderblue:0xb0e0e6,
|
||||
purple:0x800080,
|
||||
red:0xff0000,
|
||||
rosybrown:0xbc8f8f,
|
||||
royalblue:0x4169e1,
|
||||
saddlebrown:0x8b4513,
|
||||
salmon:0xfa8072,
|
||||
sandybrown:0xf4a460,
|
||||
seagreen:0x2e8b57,
|
||||
seashell:0xfff5ee,
|
||||
sienna:0xa0522d,
|
||||
silver:0xc0c0c0,
|
||||
skyblue:0x87ceeb,
|
||||
slateblue:0x6a5acd,
|
||||
slategray:0x708090,
|
||||
snow:0xfffafa,
|
||||
springgreen:0x00ff7f,
|
||||
steelblue:0x4682b4,
|
||||
tan:0xd2b48c,
|
||||
teal:0x008080,
|
||||
thistle:0xd8bfd8,
|
||||
tomato:0xff6347,
|
||||
turquoise:0x40e0d0,
|
||||
violet:0xee82ee,
|
||||
wheat:0xf5deb3,
|
||||
white:0xffffff,
|
||||
whitesmoke:0xf5f5f5,
|
||||
yellow:0xffff00,
|
||||
yellowgreen:0x9acd32 },
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
243
js/iD/styleparser/Style.js
Executable file
243
js/iD/styleparser/Style.js
Executable file
@@ -0,0 +1,243 @@
|
||||
// iD/styleparser/Style.js
|
||||
// Entity classes for iD
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array'], function(declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Style base class
|
||||
// don't use deepCopy, use lang.clone instead
|
||||
// evals not done yet
|
||||
// fillStyler not done for text yet
|
||||
|
||||
declare("iD.styleparser.Style", null, {
|
||||
merged: false,
|
||||
edited: false,
|
||||
sublayer: 5,
|
||||
interactive: true,
|
||||
properties: [],
|
||||
styleType: 'Style',
|
||||
evals: null,
|
||||
|
||||
constructor: function(){
|
||||
this.evals={};
|
||||
},
|
||||
|
||||
drawn: function(){
|
||||
return false;
|
||||
},
|
||||
|
||||
has: function(k){
|
||||
return this.properties.indexOf(k)>-1;
|
||||
},
|
||||
|
||||
mergeWith: function(additional) {
|
||||
for (var prop in this.properties) {
|
||||
if (additional[prop]) {
|
||||
this[prop]=additional[prop];
|
||||
}
|
||||
}
|
||||
this.merged=true;
|
||||
},
|
||||
|
||||
setPropertyFromString: function(k,v,isEval) {
|
||||
this.edited=true;
|
||||
if (isEval) { this.evals[k]=v; return; }
|
||||
|
||||
if (typeof(this[k])=='boolean') {
|
||||
v=Boolean(v);
|
||||
} else if (typeof(this[k])=='number') {
|
||||
v=Number(v);
|
||||
} else if (this[k] && this[k].constructor==Array) {
|
||||
v=v.split(',').map(function(a) { return Number(a); });
|
||||
}
|
||||
this[k]=v;
|
||||
return true;
|
||||
},
|
||||
|
||||
runEvals: function(tags) {
|
||||
for (var k in this.evals) {
|
||||
this.setPropertyFromString(k,eval("with (tags) {"+this.evals[k]+"}"),false);
|
||||
}
|
||||
},
|
||||
|
||||
dojoColor: function(rgb,a) {
|
||||
var b=rgb % 256;
|
||||
var g=(rgb-b) % 65536;
|
||||
var r=(rgb-b-g) % 16777216;
|
||||
return new dojo.Color([r/65536,g/256,b,a]);
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
var str='';
|
||||
for (var k in this.properties) {
|
||||
if (this.hasOwnProperty(k)) { str+=k+"="+this[k]+"; "; }
|
||||
}
|
||||
return str;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// InstructionStyle class
|
||||
|
||||
declare("iD.styleparser.InstructionStyle", [iD.styleparser.Style], {
|
||||
set_tags:null,
|
||||
breaker:false,
|
||||
styleType: 'InstructionStyle',
|
||||
addSetTag:function(k,v) {
|
||||
this.edited=true;
|
||||
if (!this.set_tags) this.set_tags={};
|
||||
this.set_tags[k]=v;
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// PointStyle class
|
||||
|
||||
declare("iD.styleparser.PointStyle", [iD.styleparser.Style], {
|
||||
properties: ['icon_image','icon_width','icon_height','rotation'],
|
||||
icon_image: null,
|
||||
icon_width: 0,
|
||||
icon_height: NaN,
|
||||
rotation: NaN,
|
||||
styleType: 'PointStyle',
|
||||
drawn:function() {
|
||||
return (this.icon_image!=null);
|
||||
},
|
||||
maxwidth:function() {
|
||||
return this.evals['icon_width'] ? 0 : this.icon_width;
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ShapeStyle class
|
||||
|
||||
declare("iD.styleparser.ShapeStyle", [iD.styleparser.Style], {
|
||||
properties: ['width','color','opacity','dashes','linecap','linejoin','line_style',
|
||||
'fill_image','fill_color','fill_opacity','casing_width','casing_color','casing_opacity','casing_dashes','layer'],
|
||||
|
||||
width:0, color:NaN, opacity:NaN, dashes:[],
|
||||
linecap:null, linejoin:null, line_style:null,
|
||||
fill_image:null, fill_color:NaN, fill_opacity:NaN,
|
||||
casing_width:NaN, casing_color:NaN, casing_opacity:NaN, casing_dashes:[],
|
||||
|
||||
layer:NaN, // optional layer override (usually set by OSM tag)
|
||||
styleType: 'ShapeStyle',
|
||||
|
||||
drawn:function() {
|
||||
return (this.fill_image || !isNaN(this.fill_color) || this.width || this.casing_width);
|
||||
},
|
||||
maxwidth:function() {
|
||||
// If width is set by an eval, then we can't use it to calculate maxwidth, or it'll just grow on each invocation...
|
||||
if (this.evals['width'] || this.evals['casing_width']) { return 0; }
|
||||
return (this.width + (this.casing_width ? this.casing_width*2 : 0));
|
||||
},
|
||||
strokeStyler:function() {
|
||||
var cap,join;
|
||||
switch (this.linecap ) { case 'round': cap ='round'; break; case 'square': cap='square'; break; default: cap ='butt' ; break; }
|
||||
switch (this.linejoin) { case 'bevel': join='bevel'; break; case 'miter' : join=4 ; break; default: join='round'; break; }
|
||||
return {
|
||||
color: this.dojoColor(this.color ? this.color : 0, this.opacity ? this.opacity : 1),
|
||||
style: 'Solid', // needs to parse dashes
|
||||
width: this.width,
|
||||
cap: cap,
|
||||
join: join
|
||||
};
|
||||
},
|
||||
shapeStrokeStyler:function() {
|
||||
if (isNaN(this.casing_color)) { return { width:0 }; }
|
||||
return {
|
||||
color: this.dojoColor(this.casing_color, this.casing_opacity ? this.casing_opacity : 1),
|
||||
width: this.casing_width ? this.casing_width : 1
|
||||
};
|
||||
},
|
||||
shapeFillStyler:function() {
|
||||
if (isNaN(this.color)) { return null; }
|
||||
return this.dojoColor(this.color, this.opacity ? this.opacity : 1);
|
||||
},
|
||||
fillStyler:function() {
|
||||
return this.dojoColor(this.fill_color, this.fill_opacity ? this.fill_opacity : 1);
|
||||
},
|
||||
casingStyler:function() {
|
||||
var cap,join;
|
||||
switch (this.linecap ) { case 'round': cap ='round'; break; case 'square': cap='square'; break; default: cap ='butt' ; break; }
|
||||
switch (this.linejoin) { case 'bevel': join='bevel'; break; case 'miter' : join=4 ; break; default: join='round'; break; }
|
||||
return {
|
||||
color: this.dojoColor(this.casing_color ? this.casing_color : 0, this.casing_opacity ? this.casing_opacity : 1),
|
||||
width: this.width+this.casing_width*2,
|
||||
style: 'Solid',
|
||||
cap: cap,
|
||||
join: join
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// TextStyle class
|
||||
|
||||
declare("iD.styleparser.TextStyle", [iD.styleparser.Style], {
|
||||
|
||||
properties: ['font_family','font_bold','font_italic','font_caps','font_underline','font_size',
|
||||
'text_color','text_offset','max_width',
|
||||
'text','text_halo_color','text_halo_radius','text_center',
|
||||
'letter_spacing'],
|
||||
|
||||
font_family: null,
|
||||
font_bold: false,
|
||||
font_italic: false,
|
||||
font_underline: false,
|
||||
font_caps: false,
|
||||
font_size: NaN,
|
||||
text_color: NaN,
|
||||
text_offset: NaN,
|
||||
max_width: NaN,
|
||||
text: null,
|
||||
text_halo_color: NaN,
|
||||
text_halo_radius: 0,
|
||||
text_center: true,
|
||||
letter_spacing: 0,
|
||||
styleType: 'TextStyle',
|
||||
|
||||
drawn: function() {
|
||||
return (this.text!=null);
|
||||
},
|
||||
fontStyler:function() {
|
||||
return {
|
||||
family: this.font_family ? this.font_family : 'Arial',
|
||||
size: this.font_size ? this.font_size*2 : '10px' ,
|
||||
weight: this.font_bold ? 'bold' : 'normal',
|
||||
style: this.font_italic ? 'italic' : 'normal'
|
||||
};
|
||||
},
|
||||
textStyler:function(_text) {
|
||||
return {
|
||||
decoration: this.font_underline ? 'underline' : 'none',
|
||||
align: 'middle',
|
||||
text: _text,
|
||||
};
|
||||
},
|
||||
fillStyler:function() {
|
||||
// not implemented yet
|
||||
return this.dojoColor(0,1);
|
||||
},
|
||||
|
||||
// getTextFormat, getHaloFilter, writeNameLabel
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ShieldStyle class
|
||||
|
||||
declare("iD.styleparser.ShieldStyle", [iD.styleparser.Style], {
|
||||
properties: ['shield_image','shield_width','shield_height'],
|
||||
shield_image: null,
|
||||
shield_width: NaN,
|
||||
shield_height: NaN,
|
||||
styleType: 'ShieldStyle',
|
||||
drawn:function() {
|
||||
return (shield_image!=null);
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
86
js/iD/styleparser/StyleChooser.js
Executable file
86
js/iD/styleparser/StyleChooser.js
Executable file
@@ -0,0 +1,86 @@
|
||||
// iD/styleparser/StyleChooser.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','iD/styleparser/RuleChain'], function(declare,lang){
|
||||
|
||||
declare("iD.styleparser.StyleChooser", null, {
|
||||
|
||||
// UpdateStyles doesn't support image-widths yet
|
||||
// or setting maxwidth/_width
|
||||
|
||||
ruleChains:[], // array of RuleChains (each one an array of Rules)
|
||||
styles:[], // array of ShapeStyle/ShieldStyle/TextStyle/PointStyle
|
||||
zoomSpecific:false, // are any of the rules zoom-specific?
|
||||
|
||||
rcpos:0,
|
||||
stylepos:0,
|
||||
|
||||
constructor:function() {
|
||||
this.ruleChains=[new iD.styleparser.RuleChain()];
|
||||
this.styles=[];
|
||||
},
|
||||
|
||||
currentChain:function() {
|
||||
return this.ruleChains[this.ruleChains.length-1];
|
||||
},
|
||||
|
||||
newRuleChain:function() {
|
||||
// starts a new ruleChain in this.ruleChains
|
||||
if (this.ruleChains[this.ruleChains.length-1].length()>0) {
|
||||
this.ruleChains.push(new iD.styleparser.RuleChain());
|
||||
}
|
||||
},
|
||||
|
||||
addStyles:function(a) {
|
||||
this.styles=this.styles.concat(a);
|
||||
},
|
||||
|
||||
updateStyles:function(entity, tags, sl, zoom) {
|
||||
if (this.zoomSpecific) { sl.validAt=zoom; }
|
||||
|
||||
// Are any of the ruleChains fulfilled?
|
||||
var w;
|
||||
for (var i in this.ruleChains) {
|
||||
var c=this.ruleChains[i];
|
||||
if (c.test(-1, entity, tags, zoom)) {
|
||||
sl.addSubpart(c.subpart);
|
||||
|
||||
// Update StyleList
|
||||
for (var j in this.styles) {
|
||||
var r=this.styles[j];
|
||||
var a;
|
||||
switch (r.styleType) {
|
||||
|
||||
case 'ShapeStyle' : sl.maxwidth=Math.max(sl.maxwidth,r.maxwidth());
|
||||
a=sl.shapeStyles; break;
|
||||
case 'ShieldStyle': a=sl.shieldStyles; break;
|
||||
case 'TextStyle' : a=sl.textStyles; break;
|
||||
case 'PointStyle' : sl.maxwidth=Math.max(sl.maxwidth,r.maxwidth());
|
||||
a=sl.pointStyles; break;
|
||||
case 'InstructionStyle':
|
||||
if (InstructionStyle(r).breaker) { return; }
|
||||
for (var k in InstructionStyle(r).set_tags) { tags[k]=InstructionStyle(r).set_tags[k]; }
|
||||
break;
|
||||
}
|
||||
if (r.drawn) { tags[':drawn']='yes'; }
|
||||
tags['_width']=sl.maxwidth;
|
||||
|
||||
r.runEvals(tags);
|
||||
if (a[c.subpart]) {
|
||||
// If there's already a style on this sublayer, then merge them
|
||||
// (making a deep copy if necessary to avoid altering the root style)
|
||||
if (!a[c.subpart].merged) { a[c.subpart]=lang.clone(a[c.subpart]); }
|
||||
a[c.subpart].mergeWith(r);
|
||||
} else {
|
||||
// Otherwise, just assign it
|
||||
a[c.subpart]=r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
84
js/iD/styleparser/StyleList.js
Executable file
84
js/iD/styleparser/StyleList.js
Executable file
@@ -0,0 +1,84 @@
|
||||
// iD/styleparser/StyleList.js
|
||||
|
||||
define(['dojo/_base/declare'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// StyleList class
|
||||
// Not tested yet!
|
||||
|
||||
// A StyleList object is the full list of all styles applied to
|
||||
// a drawn entity (i.e. node/way).
|
||||
// Each array element applies to that sublayer (z-index). If there
|
||||
// is no element, nothing is drawn on that sublayer.
|
||||
// StyleLists are created by StyleChooser.getStyles.
|
||||
|
||||
declare("iD.styleparser.StyleList", null, {
|
||||
|
||||
shapeStyles:{},
|
||||
textStyles:{},
|
||||
pointStyles:{},
|
||||
shieldStyles:{},
|
||||
maxwidth:0,
|
||||
subparts:[], // List of subparts used in this StyleList
|
||||
validAt:-1, // Zoom level this is valid at (or -1 at all levels - saves recomputing)
|
||||
|
||||
constructor:function() {
|
||||
this.shapeStyles={};
|
||||
this.textStyles={};
|
||||
this.pointStyles={};
|
||||
this.shieldStyles={};
|
||||
this.subparts=[];
|
||||
},
|
||||
|
||||
// Does this StyleList contain any styles?
|
||||
hasStyles:function() {
|
||||
return ( this.hasShapeStyles() || this.hasTextStyles() || this.hasPointStyles() || this.hasShieldStyles() );
|
||||
},
|
||||
|
||||
// Does this StyleList contain any styles with a fill?
|
||||
hasFills:function() {
|
||||
for (var s in this.shapeStyles) {
|
||||
if (!isNaN(this.shapeStyles(s).fill_color) || this.shapeStyles(s).fill_image) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Does this StyleList manually force an OSM layer?
|
||||
layerOverride:function() {
|
||||
for (var s in this.shapeStyles) {
|
||||
if (!isNaN(this.shapeStyles[s].layer)) return this.shapeStyles[s].layer;
|
||||
}
|
||||
return NaN;
|
||||
},
|
||||
|
||||
// Record that a subpart is used in this StyleList.
|
||||
addSubpart:function(s) {
|
||||
if (this.subparts.indexOf(s)==-1) { this.subparts.push(s); }
|
||||
},
|
||||
|
||||
// Is this StyleList valid at a given zoom?
|
||||
isValidAt:function(zoom) {
|
||||
return (this.validAt==-1 || this.validAt==zoom);
|
||||
},
|
||||
|
||||
// Summarise StyleList as String - for debugging
|
||||
toString:function() {
|
||||
var str='';
|
||||
var k;
|
||||
for (k in this.shapeStyles ) { str+="- SS "+k+"="+this.shapeStyles[k]+"\n"; }
|
||||
for (k in this.textStyles ) { str+="- TS "+k+"="+this.textStyles[k]+"\n"; }
|
||||
for (k in this.pointStyles ) { str+="- PS "+k+"="+this.pointStyles[k]+"\n"; }
|
||||
for (k in this.shieldStyles) { str+="- sS "+k+"="+this.shieldStyles[k]+"\n"; }
|
||||
return str;
|
||||
},
|
||||
|
||||
hasShapeStyles:function() { for (var a in shapeStyles ) { return true; }; return false; },
|
||||
hasTextStyles:function() { for (var a in textStyles ) { return true; }; return false; },
|
||||
hasPointStyles:function() { for (var a in pointStyles ) { return true; }; return false; },
|
||||
hasShieldStyles:function() { for (var a in shieldStyles) { return true; }; return false; },
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
97
js/iD/ui/DragAndDrop.js
Normal file
97
js/iD/ui/DragAndDrop.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// iD/ui/DragAndDrop.js
|
||||
|
||||
/*
|
||||
Singleton-like class for POI drag and drop.
|
||||
Could potentially be a ControllerState.
|
||||
*/
|
||||
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/xhr','dojo/dom','dojo/dom-geometry','dojo/dnd/Target'], function(declare,lang,xhr,dom,domGeom){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// DragAndDrop class
|
||||
|
||||
declare("iD.ui.DragAndDrop", null, {
|
||||
|
||||
mapdiv:null,
|
||||
map:null,
|
||||
divname:"",
|
||||
grid:null,
|
||||
dragmove:null,
|
||||
dragevent:null,
|
||||
|
||||
ICONPATH: 'icons/',
|
||||
ITEMSPERROW: 5,
|
||||
|
||||
constructor:function(_divname,_map,_gridname) {
|
||||
this.divname=_divname;
|
||||
dom.byId(_divname).ondragover = lang.hitch(this,this.update);
|
||||
dom.byId(_divname).ondrop = function(e) { e.preventDefault(); }; // required by Firefox
|
||||
this.map=_map;
|
||||
this.grid=dom.byId(_gridname);
|
||||
|
||||
// Load drag and drop config file
|
||||
dojo.xhrGet({
|
||||
url: "draganddrop.json",
|
||||
handleAs: "json",
|
||||
load: lang.hitch(this, function(obj) { this.drawGrid(obj); } ),
|
||||
error: function(err) { console.log("couldn't load"); }
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
drawGrid:function(obj) {
|
||||
var row;
|
||||
for (var i=0; i<obj.length; i++) {
|
||||
var item=obj[i];
|
||||
if (!row || row.length==this.ITEMSPERROW) {
|
||||
row=document.createElement('tr');
|
||||
this.grid.appendChild(row);
|
||||
}
|
||||
var cell=document.createElement('td');
|
||||
var img=document.createElement('img');
|
||||
img.setAttribute('src',this.ICONPATH+item.icon)
|
||||
img.setAttribute('alt',item.tags);
|
||||
img.setAttribute('draggable',true);
|
||||
img.ondragend=lang.hitch(this,this.end);
|
||||
img.style.float='left';
|
||||
cell.appendChild(img);
|
||||
cell.appendChild(document.createTextNode(item.name));
|
||||
row.appendChild(cell);
|
||||
}
|
||||
},
|
||||
|
||||
update:function(event) {
|
||||
this.dragevent=event;
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
end:function(event) {
|
||||
var lon=this.map.coord2lon(this.map.mouseX(this.dragevent));
|
||||
var lat=this.map.coord2lat(this.map.mouseY(this.dragevent));
|
||||
var tags=this.parseKeyValues(event.target.getAttribute('alt'));
|
||||
|
||||
var action=new iD.actions.CreatePOIAction(this.map.conn,tags,lat,lon);
|
||||
this.map.controller.undoStack.addAction(action);
|
||||
var node=action.getNode();
|
||||
this.map.createUI(node);
|
||||
|
||||
dijit.byId('addPOI').closeDropDown();
|
||||
this.map.controller.setState(new iD.controller.edit.SelectedPOINode(node));
|
||||
},
|
||||
|
||||
parseKeyValues:function(string) {
|
||||
var pairs=string.split(';');
|
||||
var tags={};
|
||||
for (var i in pairs) {
|
||||
var kv=pairs[i].split('=');
|
||||
tags[kv[0]]=kv[1];
|
||||
}
|
||||
return tags;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
79
js/iD/ui/StepPane.js
Normal file
79
js/iD/ui/StepPane.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// iD/ui/StepPane.js
|
||||
|
||||
/*
|
||||
Step-by-step help pane.
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang'], function(declare,lang){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// StepPane class
|
||||
|
||||
// ******
|
||||
// This is a bit messy at present - it shouldn't take stepsname or similar, it should just
|
||||
// create the pane programmatically.
|
||||
// ******
|
||||
// We should also be able to set the title of the pane.
|
||||
|
||||
declare("iD.ui.StepPane", null, {
|
||||
|
||||
divname:null,
|
||||
stepsname:null, // we probably don't want to have this
|
||||
currentStep:0,
|
||||
|
||||
constructor:function(_divname,_stepsdivname) {
|
||||
this.divname=_divname;
|
||||
this.stepsname=_stepsdivname;
|
||||
},
|
||||
|
||||
// Getters for the <div> containing the steps, and its individual nodes
|
||||
|
||||
stepsDiv:function() { return document.getElementById(this.stepsname); },
|
||||
stepsNodes:function() { return this.stepsDiv().childNodes; },
|
||||
|
||||
// Add/remove steps
|
||||
|
||||
addStep:function(text) {
|
||||
this.stepsDiv().appendChild(document.createElement('li')).innerHTML=text;
|
||||
},
|
||||
setStep:function(pos,text) {
|
||||
if (this.stepsNodes().length<pos) { addStep(text); }
|
||||
else { this.stepsNodes()[pos].innerHTML=text; }
|
||||
},
|
||||
setSteps:function(steps) {
|
||||
this.clear();
|
||||
for (var i=0; i<steps.length; i++) {
|
||||
this.addStep(steps[i]);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
clear:function() {
|
||||
for (var i=this.stepsNodes().length-1; i>=1; i--) {
|
||||
this.stepsDiv().removeChild(this.stepsNodes()[i]);
|
||||
}
|
||||
},
|
||||
|
||||
// Change the highlighted step
|
||||
|
||||
highlight:function(step) {
|
||||
this.show();
|
||||
this.currentStep=step;
|
||||
for (var i=1; i<this.stepsNodes().length; i++) {
|
||||
this.stepsNodes()[i].style.color = i==step ? 'black' : 'lightgray';
|
||||
}
|
||||
},
|
||||
|
||||
// Show/hide window
|
||||
|
||||
show:function() {
|
||||
dijit.byId(this.divname).show();
|
||||
},
|
||||
hide:function() {
|
||||
dijit.byId(this.divname).hide();
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
16
js/lib/jshashtable.js
Executable file
16
js/lib/jshashtable.js
Executable file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright 2010 Tim Down.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();
|
||||
151
potlatch.css
Executable file
151
potlatch.css
Executable file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
|
||||
Stylesheet that mimicks, to a certain extent, potlatch 1.x
|
||||
Andy Allan, November 2009
|
||||
|
||||
Based heavily on:
|
||||
MapCSS demonstration stylesheet
|
||||
Richard Fairhurst, October 2009
|
||||
|
||||
*/
|
||||
|
||||
/* A set of fairly standard rules.
|
||||
We use z-index to make sure high-priority roads appear above minor ones.
|
||||
The default z-index is 5. If an object matches multiple rules with the same
|
||||
z-index then the rules are "merged" (but individual properties become one or the other) */
|
||||
|
||||
way[highway=motorway],way[highway=motorway_link],
|
||||
way[highway=trunk],way[highway=trunk_link],
|
||||
way[highway=primary],way[highway=primary_link],
|
||||
way[highway=secondary],way[highway=secondary_link],
|
||||
way[highway=tertiary],way[highway=tertiary_link],
|
||||
way[highway=unclassified],
|
||||
way[highway=residential] { text: name; text-color: black; font-size: 7; text-position: line;}
|
||||
way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #809BC0; width: 7; casing-color: black; casing-width: 1; }
|
||||
way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #7FC97F; width: 7; casing-color: black; casing-width: 1; }
|
||||
way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 7; casing-color: black; casing-width: 1; }
|
||||
way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #FDBF6F; width: 7; casing-width: 1; }
|
||||
way[highway=tertiary],way[highway=unclassified] { z-index: 6; color: #FEFECB; width: 5; casing-width: 1; }
|
||||
way[highway=residential] { z-index: 5; color: #E8E8E8; width: 5; casing-color: gray; casing-width: 1; }
|
||||
way[highway=service] { color: white; width: 3; casing-width: 1; }
|
||||
|
||||
/* Pedestrian precincts need to be treated carefully. Only closed-loops with an explicit
|
||||
area=yes tag should be filled. The below doesn't yet work as intended. */
|
||||
way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 1; }
|
||||
way[highway=pedestrian] :area { color: #555555; width: 1; fill-color: #ddddee; fill-opacity: 0.8; }
|
||||
|
||||
way[highway=steps] { color: #FF6644; width: 2; dashes: 4, 2; }
|
||||
way[highway=footway] { color: #FF6644; width: 2; dashes: 6, 3; }
|
||||
way[highway=bridleway] { z-index:9; color: #996644; width: 2; dashes: 4, 2, 2, 2; }
|
||||
way[highway=track] { color: #996644; width: 2; dashes: 4, 2; }
|
||||
way[highway=path] { color: lightgreen; width: 2; dashes: 2, 2; }
|
||||
|
||||
way[waterway=river], way[waterway=canal] { color: blue; width: 2; text:name; text-color:blue; font-size:9; text-position: offset; text-offset: 7;}
|
||||
|
||||
way[barrier] {color: #000000; width: 1}
|
||||
|
||||
/* Fills can be solid colour or bitmap images */
|
||||
|
||||
|
||||
way[natural] :area { color: #ADD6A5; width: 1; fill-color: #ADD6A5; fill-opacity: 0.2; }
|
||||
way[landuse] :area { color: #444444; width: 2; fill-color: #444444; fill-opacity: 0.3; }
|
||||
way[amenity],way[shop] :area { color: #ADCEB5; width: 1; fill-color: #ADCEB5; fill-opacity: 0.2; }
|
||||
way[leisure],way[sport] :area { color: #8CD6B5; width: 1; fill-color: #8CD6B5; fill-opacity: 0.2; }
|
||||
way[tourism] :area { color: #F7CECE; width: 1; fill-color: #F7CECE; fill-opacity: 0.2; }
|
||||
way[historic],way[ruins] :area { color: #F7F7DE; width: 1; fill-color: #F7F7DE; fill-opacity: 0.2; }
|
||||
way[military] :area { color: #D6D6D6; width: 1; fill-color: #D6D6D6; fill-opacity: 0.2; }
|
||||
way[building] :area { color: #ff6ec7; width: 1; fill-color: #ff6ec7; fill-opacity: 0.2; }
|
||||
way[natural=water],
|
||||
way[waterway] :area { color: blue; width: 2; fill-color: blue; fill-opacity: 0.2; }
|
||||
way[landuse=forest],way[natural=wood] :area { color: green; width: 2; fill-color: green; fill-opacity: 0.2; }
|
||||
way[leisure=pitch],way[leisure=park] { color: #44ff44; width: 1; fill-color: #44ff44; fill-opacity: 0.2; }
|
||||
way[amenity=parking] :area { color: gray; width: 1; fill-color: gray; fill-opacity: 0.2; }
|
||||
way[public_transport=pay_scale_area] :area { color: gray; width: 1; fill-color: gray; fill-opacity: 0.1; }
|
||||
|
||||
/* Addressing. Nodes with addresses *and* match POIs should have a poi icon, so we put addressing first */
|
||||
|
||||
node[addr:housenumber],
|
||||
node[addr:housename] { icon-image: circle; icon-width: 4; color: #B0E0E6; casing-color:blue; casing-width: 1; }
|
||||
way[addr:interpolation] { color: #B0E0E6; width: 3; dashes: 3,3;}
|
||||
|
||||
/* POIs, too, can have bitmap icons - they can even be transparent */
|
||||
|
||||
node[amenity=pub] { icon-image: icons/pub.png; text-offset: 15; font-family: DejaVu; text: name; font-size: 9; }
|
||||
node[place] { icon-image: icons/place.png; text-offset: 17; font-family: DejaVu; text: name; font-size: 9; font-weight: bold; text-decoration: underline; }
|
||||
node[railway=station] { icon-image: icons/station.png; text-offset: 13; font-family: DejaVu; text: name; font-size: 9; font-weight: bold; }
|
||||
node[aeroway=aerodrome] { icon-image: icons/airport.png; text-offset: 13; font-family: DejaVu; text: name; font-size: 10; }
|
||||
node[amenity=atm] { icon-image: icons/atm.png; }
|
||||
node[amenity=bank] { icon-image: icons/bank.png; text-offset: 15; text: name; }
|
||||
node[highway=bus_stop] { icon-image: icons/bus_stop.png; }
|
||||
node[amenity=cafe] { icon-image: icons/cafe.png; text-offset: 15; text: name; icon-width: 16; }
|
||||
node[shop=convenience] { icon-image: icons/convenience.png; text-offset:15; text:name; }
|
||||
node[shop=supermarket] { icon-image: icons/supermarket.png; text-offset:15; text:name; }
|
||||
node[amenity=fast_food] { icon-image: icons/fast_food.png; text-offset:15; text: name; }
|
||||
node[amenity=fire_station] { icon-image: icons/fire_station.png; }
|
||||
node[amenity=hospital] { icon-image: icons/hospital.png; }
|
||||
node[tourism=hotel] { icon-image: icons/hotel.png; }
|
||||
node[amenity=parking] { icon-image: icons/parking.png; }
|
||||
node[amenity=bicycle_parking] { icon-image: icons/parking_cycle.png; text-offset: 15; text: capacity; }
|
||||
node[amenity=pharmacy] { icon-image: icons/pharmacy.png; }
|
||||
node[amenity=pharmacy][dispensing=yes] { icon-image: icons/pharmacy_dispensing.png; }
|
||||
node[amenity=police] { icon-image: icons/police.png; }
|
||||
node[amenity=post_box] { icon-image: icons/post_box.png; }
|
||||
node[amenity=recycling] { icon-image: icons/recycling.png; }
|
||||
node[amenity=restaurant] { icon-image: icons/restaurant.png; icon-width: 16; }
|
||||
node[amenity=school] { icon-image: icons/school.png; icon-width: 16; }
|
||||
node[amenity=taxi] { icon-image: icons/taxi.png; }
|
||||
node[amenity=telephone] { icon-image: icons/telephone.png; }
|
||||
way node[barrier=gate], way node[highway=gate] { icon-image: icons/gate.png; }
|
||||
|
||||
/* We can stack styles at different z-index (depth) */
|
||||
|
||||
way[railway=rail]
|
||||
{ z-index: 6; color: black; width: 5; }
|
||||
{ z-index: 7; color: white; width: 3; dashes: 12,12; }
|
||||
way[railway=subway]
|
||||
{ z-index: 6; color: #444444; width: 5; }
|
||||
{ z-index: 7; color: white; width: 3; dashes: 8,8; }
|
||||
|
||||
/* Bridge */
|
||||
way[bridge=yes]
|
||||
{ z-index: 4; color: white; width: eval('_width+3'); }
|
||||
{ z-index: 3; color: black; width: eval('_width+6'); }
|
||||
|
||||
/* Tunnel */
|
||||
way[tunnel=yes]
|
||||
{ z-index: 4; color: white; width: eval('_width+2'); }
|
||||
{ z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; }
|
||||
|
||||
/* Oneway */
|
||||
way[oneway=yes] { z-index: 10; color: #444444; width: 3; dashes: 15,25; line-style: arrows; }
|
||||
|
||||
|
||||
/* Change the road colour based on dynamically set "highlighted" tag (see earlier) */
|
||||
|
||||
way .highlighted { color: pink; }
|
||||
|
||||
/* Interactive editors may choose different behaviour when a user mouses-over or selects
|
||||
an object. Potlatch 2 supports these but the stand-alone Halcyon viewer does not */
|
||||
|
||||
way !:drawn { z-index:10; width: 1; color: black; }
|
||||
way::highlight :hover { z-index: 2; width: eval('_width+10'); color: #ffff99; }
|
||||
way::highlight :selected { z-index: 2; width: eval('_width+10'); color: yellow; opacity: 0.7;}
|
||||
|
||||
node :selectedway { z-index: 9; icon-image: square; icon-width: 8; color: red; }
|
||||
node::junction :junction :selectedway { z-index: 8; icon-image: square; icon-width: 11; casing-color: black; casing-width: 1; }
|
||||
|
||||
node !:drawn :poi { z-index: 2; icon-image: circle; icon-width: 4; color: green; casing-color: black; casing-width: 1; }
|
||||
|
||||
node :hoverway { z-index: 9; icon-image: square; icon-width: 7; color: blue; }
|
||||
node::highlight :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; }
|
||||
|
||||
/* Descendant selectors provide an easy way to style relations: this example means "any way
|
||||
which is part of a relation whose type=route". */
|
||||
|
||||
relation[type=route] way { z-index: 1; width: 17; color: blue; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=ncn] way { z-index: 1; width: 12; color: red; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=rcn] way { z-index: 1; width: 12; color: cyan; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=lcn] way { z-index: 1; width: 12; color: blue; opacity: 0.3; }
|
||||
relation[type=route][route=foot] way { z-index: 1; width: 10; color: #80ff80; opacity: 0.6; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user