mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-31 09:19:25 +02:00
54
css/app.css
54
css/app.css
@@ -5,11 +5,27 @@ body {
|
||||
text-rendering: optimizeLegibility;
|
||||
color:#444;
|
||||
}
|
||||
|
||||
form {
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline-color: transparent;
|
||||
outline-style: none;
|
||||
}
|
||||
#about {
|
||||
position:absolute;
|
||||
bottom:5px;
|
||||
right:5px;
|
||||
background:#fff;
|
||||
padding:2px 5px;
|
||||
}
|
||||
p {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-size: x-small;
|
||||
}
|
||||
a:visited, a {
|
||||
@@ -25,12 +41,6 @@ input[type=text]:focus {
|
||||
border-color:#222;
|
||||
}
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
text {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
@@ -44,15 +54,7 @@ table th {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 600px;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
.help-pane {
|
||||
position:absolute;
|
||||
@@ -77,7 +79,7 @@ table th {
|
||||
}
|
||||
|
||||
#modebuttons {
|
||||
width:500px;
|
||||
width:600px;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
@@ -96,9 +98,10 @@ table th {
|
||||
#modebuttons button {
|
||||
width:100px;
|
||||
cursor:pointer;
|
||||
display:inline-block;
|
||||
background:#fff;
|
||||
color:#555;
|
||||
font:bold 19px/30px 'Helvetica Neue';
|
||||
font:bold 19px/30px 'Helvetica Neue', sans-serif;
|
||||
border:0;
|
||||
border-right:1px solid #222;
|
||||
border-bottom:1px solid #222;
|
||||
@@ -107,6 +110,19 @@ table th {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#modebuttons button.mini,
|
||||
#modebuttons button.mini {
|
||||
width:50px;
|
||||
}
|
||||
|
||||
#modebuttons input[type=text] {
|
||||
width:150px;
|
||||
height:30px;
|
||||
border:0;
|
||||
padding:5px;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#modebuttons button:hover {
|
||||
background:#eee;
|
||||
}
|
||||
@@ -146,10 +162,6 @@ table th {
|
||||
width:135px;
|
||||
}
|
||||
|
||||
polyline {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
position:absolute;
|
||||
display:none;
|
||||
|
||||
BIN
icons/generic.png
Normal file
BIN
icons/generic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 734 B |
257
index.html
257
index.html
@@ -1,104 +1,141 @@
|
||||
<!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.8/dijit/themes/claro/claro.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.8/dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true, baseUrl: 'js/iD/'"></script>
|
||||
</head>
|
||||
<body class="claro">
|
||||
<div id="appLayout" class="demoLayout">
|
||||
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/lib/jquery-1.8.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/iD/actions/UndoStack.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Util.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Taginfo.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Node.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Relation.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Entity.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Way.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Connection.js"></script>
|
||||
<script>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>iD</title>
|
||||
<link rel="stylesheet" href="css/map.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/lib/jquery-1.8.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/lib/d3.v2.min.js"></script>
|
||||
<script type="text/javascript" src="js/iD/actions/UndoStack.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Util.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Taginfo.js"></script>
|
||||
|
||||
require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented",
|
||||
"iD/actions/UndoStack",
|
||||
"iD/actions/CreatePOIAction",
|
||||
"iD/actions/AddNodeToWayAction",
|
||||
"iD/Controller",
|
||||
"iD/actions/CreateEntityAction",
|
||||
"iD/controller/edit/NoSelection",
|
||||
"iD/controller/shape/NoSelection",
|
||||
"iD/renderer/Map","iD/styleparser/RuleSet",
|
||||
"iD/ui/DragAndDrop","iD/ui/StepPane",
|
||||
"dojo/domReady!"], function(domGeom,domClass,on,dom,Evented){
|
||||
<script type="text/javascript" src="js/iD/controller/controller.js"></script>
|
||||
|
||||
var ruleset = new iD.styleparser.RuleSet();
|
||||
var connection = new iD.Connection("http://www.overpass-api.de/api/xapi?");
|
||||
<script type="text/javascript" src="js/iD/controller/edit/edit.js"></script>
|
||||
<script type="text/javascript" src="js/iD/controller/edit/EditBaseState.js"></script>
|
||||
<script type="text/javascript" src="js/iD/controller/edit/NoSelection.js"></script>
|
||||
<script type="text/javascript" src="js/iD/controller/edit/SelectedPOINode.js"></script>
|
||||
<script type="text/javascript" src="js/iD/controller/edit/SelectedWay.js"></script>
|
||||
<script type="text/javascript" src="js/iD/controller/edit/SelectedWayNode.js"></script>
|
||||
|
||||
// Load styles
|
||||
ruleset.registerCallback(styleLoaded);
|
||||
ruleset.loadFromCSS("potlatch.css", styleLoaded);
|
||||
<script type="text/javascript" src="js/iD/renderer/renderer.js"></script>
|
||||
<script type="text/javascript" src="js/iD/renderer/Map.js"></script>
|
||||
|
||||
// Initialise map
|
||||
var map = new iD.renderer.Map({
|
||||
lat: 51.87,
|
||||
lon: -1.49,
|
||||
zoom: 17,
|
||||
div: "map",
|
||||
connection: connection,
|
||||
width: dom.byId('map').offsetWidth,
|
||||
height: dom.byId('map').offsetHeight
|
||||
});
|
||||
map.ruleset = ruleset;
|
||||
<script type="text/javascript" src="js/iD/Node.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Relation.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Entity.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Way.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Connection.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Controller.js"></script>
|
||||
|
||||
// Initialise controller
|
||||
var controller = new iD.Controller(map);
|
||||
map.setController(controller);
|
||||
<div id='modebuttons'>
|
||||
<button id='add-place'>
|
||||
+ Place</button><!-- <button id="add-road">
|
||||
+ Road</button><button id="add-area">
|
||||
+ Area</button>--><button class='mini' id="undo">
|
||||
←</button><button class='mini' id="redo">
|
||||
→</button><form id='geocode-form'><input type='text' id='geocode-location' placeholder='find a place' />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Data is loaded and app ready to go
|
||||
function styleLoaded() {
|
||||
// Initialise drag-and-drop icons
|
||||
new iD.ui.DragAndDrop("map", map, "dndgrid");
|
||||
<div id="zoombuttons">
|
||||
<button id="zoomIn">+</button><button id="zoomOut">–</button>
|
||||
</div>
|
||||
|
||||
// Initialise help pane
|
||||
controller.setStepper(new iD.ui.StepPane());
|
||||
<!-- Floating edit pane -->
|
||||
<div class='edit-pane'>
|
||||
<h2> </h2>
|
||||
<a class='close' href='#close'>×</a>
|
||||
<div class='hud tags'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='hud presets'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Set initial controllerState
|
||||
controller.setState(new iD.controller.edit.NoSelection());
|
||||
<div id="map"></div>
|
||||
<div id='about'>
|
||||
<p>Work in progress: <a href='http://www.geowiki.com/'>introduction</a>,
|
||||
<a href='http://github.com/systemed/iD'>code</a>,
|
||||
<a href='http://www.geowiki.com/docs'>docs</a>.
|
||||
Imagery <a href="http://opengeodata.org/microsoft-imagery-details">© 2012</a> Bing, GeoEye, Getmapping, Intermap, Microsoft.</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var connection = new iD.Connection("http://www.overpass-api.de/api/xapi?");
|
||||
|
||||
// Load data
|
||||
map.download();
|
||||
}
|
||||
// Initialise map
|
||||
var map = iD.renderer.Map({
|
||||
selector: "#map",
|
||||
connection: connection,
|
||||
width: $(document).width(),
|
||||
height: $(document).height()
|
||||
});
|
||||
map.setZoom(19)
|
||||
.setCentre({ lat: 40.7965621, lon: -74.4773184 });
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Mode button handlers
|
||||
$('#add-place').click(function() {
|
||||
controller.setState(new iD.controller.shape.NoSelection('node'));
|
||||
// ----------------------------------------------------
|
||||
// Data is loaded and app ready to go
|
||||
// Set initial controllerState
|
||||
map.controller.setState(new iD.controller.edit.NoSelection());
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Mode button handlers
|
||||
d3.select('#add-place').on('click', function() {
|
||||
map.controller.setState(new iD.controller.shape.NoSelection('node'));
|
||||
});
|
||||
|
||||
$('#add-road').click(function() {
|
||||
controller.setState(new iD.controller.shape.NoSelection('way'));
|
||||
d3.select('#add-road').on('click', function() {
|
||||
map.controller.setState(new iD.controller.shape.NoSelection('way'));
|
||||
});
|
||||
|
||||
$('#add-area').click(function() {
|
||||
controller.setState(new iD.controller.shape.NoSelection());
|
||||
d3.select('#add-area').on('click', function() {
|
||||
map.controller.setState(new iD.controller.shape.NoSelection());
|
||||
});
|
||||
|
||||
d3.select('#geocode-form').on('submit', function() {
|
||||
d3.event.preventDefault();
|
||||
var val = d3.select('#geocode-location').node().value;
|
||||
$.ajax({
|
||||
url: 'http://api.tiles.mapbox.com/v3/mapbox/geocode/' +
|
||||
encodeURIComponent(val) + '.jsonp',
|
||||
dataType: 'jsonp',
|
||||
jsonpCallback: 'grid',
|
||||
success: function(resp) {
|
||||
map.setCentre({
|
||||
lon: resp.results[0][0].lon,
|
||||
lat: resp.results[0][0].lat
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#undo').click(function() {
|
||||
controller.undoStack.undo();
|
||||
map.controller.undoStack.undo();
|
||||
map.updateUIs(true, true);
|
||||
});
|
||||
|
||||
$('#redo').click(function() {
|
||||
controller.undoStack.redo();
|
||||
controller.undoStack.redo();
|
||||
map.updateUIs(true, true);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Map control handlers
|
||||
// ----------------------------------------------------
|
||||
// Map control handlers
|
||||
|
||||
$('#zoomIn').click(function() {
|
||||
map.zoomIn();
|
||||
@@ -107,76 +144,6 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented
|
||||
$('#zoomOut').click(function() {
|
||||
map.zoomOut();
|
||||
});
|
||||
|
||||
var scroll = 0;
|
||||
$('#map').bind('mousewheel', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
scroll += e.originalEvent.wheelDelta;
|
||||
if (scroll > 300) {
|
||||
map.zoomIn();
|
||||
scroll = 0;
|
||||
}
|
||||
if (scroll < -300) {
|
||||
map.zoomOut();
|
||||
scroll = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id='modebuttons'>
|
||||
<button id='add-place'>
|
||||
+ Place</button><button id="add-road">
|
||||
+ Road</button><button id="add-area">
|
||||
+ Area</button><button id="undo">
|
||||
←</button><button id="redo">
|
||||
→</button>
|
||||
|
||||
|
||||
<div id='addPOI'>
|
||||
<table id='dndgrid'>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="zoombuttons">
|
||||
<button id="zoomIn">+</button><button id="zoomOut">–</button>
|
||||
</div>
|
||||
|
||||
<!-- Floating help window -->
|
||||
<div class='help-pane' id='road-help'>
|
||||
<div>Click on the map to start a road</div>
|
||||
<div>Draw the road by clicking on points along its path</div>
|
||||
<div>Choose a road type</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating edit pane -->
|
||||
<div class='edit-pane'>
|
||||
<h2> </h2>
|
||||
<a class='close' href='#close'>×</a>
|
||||
<div class='hud tags'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='hud presets'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map div -->
|
||||
|
||||
<div id='progress'>↻</div>
|
||||
<div id="map"></div>
|
||||
<p>Work in progress: <a href='http://www.geowiki.com/'>introduction</a>, <a href='http://github.com/systemed/iD'>code</a>, <a href='http://www.geowiki.com/docs'>docs</a>. Imagery <a href="http://opengeodata.org/microsoft-imagery-details">© 2012</a> Bing, GeoEye, Getmapping, Intermap, Microsoft.</p>
|
||||
|
||||
</div><!-- applayout -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
// define(["dojo/_base/xhr","dojo/_base/lang","dojox/xml/DomParser","dojo/_base/array",'dojo/_base/declare',
|
||||
// "iD/Entity","iD/Node","iD/Way","iD/Relation","iD/actions/CreateEntityAction"],
|
||||
// function(xhr,lang,DomParser,array,declare,Entity){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Connection base class
|
||||
|
||||
if (typeof iD === 'undefined') iD = {};
|
||||
|
||||
iD.Connection = function(apiURL) {
|
||||
// summary: The data store, including methods to fetch data from (and, eventually, save data to)
|
||||
// an OSM API server.
|
||||
@@ -20,12 +14,16 @@ iD.Connection = function(apiURL) {
|
||||
|
||||
var connection = {};
|
||||
|
||||
function all() {
|
||||
return _.values(entities);
|
||||
}
|
||||
|
||||
function assign(obj) {
|
||||
// summary: Save an entity to the data store.
|
||||
switch (obj.entityType) {
|
||||
case "node": entities[obj.id] = obj; break;
|
||||
case "way": entities[obj.id] = obj; break;
|
||||
case "relation": relations[obj.id] = obj; break;
|
||||
if (obj.entityType === 'node' || obj.entityType === 'way') {
|
||||
entities[obj.id] = obj;
|
||||
} else if (obj.entityType === 'relation') {
|
||||
relations[obj.id] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,70 +62,81 @@ iD.Connection = function(apiURL) {
|
||||
return relation;
|
||||
}
|
||||
|
||||
function getObjectsByBbox(left,right,top,bottom) {
|
||||
function getObjectsByBbox(extent) {
|
||||
// summary: Find all drawable entities that are within a given bounding box.
|
||||
// Each one is an array of entities.
|
||||
var o = {
|
||||
inside: [],
|
||||
outside: []
|
||||
};
|
||||
_.each(this.entities, function(e, id) {
|
||||
if (e.within(left, right, top, bottom)) { o.inside.push(e); }
|
||||
else { o.outside.push(e); }
|
||||
return _.filter(this.entities, function(e, id) {
|
||||
return e.within(extent);
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
// ------------
|
||||
// POI handling
|
||||
function updatePOIs(nodelist) {
|
||||
// summary: Update the list of POIs (nodes not in ways) from a supplied array of nodes.
|
||||
_.each(nodelist, function(node) {
|
||||
if (node.entity.hasParentWays()) {
|
||||
delete pois[node._id];
|
||||
} else {
|
||||
pois[node._id] = node;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPOIs() {
|
||||
// summary: Return a list of all the POIs in connection Connection.
|
||||
return _.values(pois);
|
||||
}
|
||||
|
||||
function registerPOI(node) {
|
||||
// summary: Register a node as a POI (not in a way).
|
||||
pois[node._id] = node;
|
||||
}
|
||||
|
||||
function unregisterPOI(node) {
|
||||
// summary: Mark a node as no longer being a POI (it's now in a way).
|
||||
delete pois[node._id];
|
||||
}
|
||||
|
||||
// ----------
|
||||
// OSM parser
|
||||
|
||||
function loadFromAPI(box, callback) {
|
||||
// summary: Request data within the bbox from an external OSM server. Currently hardcoded
|
||||
// to use Overpass API (which has the relevant CORS headers).
|
||||
loadFromURL("http://www.overpass-api.de/api/xapi?map?bbox=" +
|
||||
[box.west, box.south, box.east, box.north], callback);
|
||||
[box[0][0], box[1][1], box[1][0], box[0][1]], callback);
|
||||
}
|
||||
|
||||
function loadFromURL(url, callback) {
|
||||
// summary: Load all data from a given URL.
|
||||
$.ajax({
|
||||
url: url,
|
||||
headers: { "X-Requested-With": null },
|
||||
success: parse(callback)
|
||||
});
|
||||
}
|
||||
|
||||
// Private functions to parse DOM created from XML file
|
||||
function filterNodeName(n) {
|
||||
return function(item) { return item.nodeName === n; };
|
||||
}
|
||||
|
||||
function getAttribute(obj, name) {
|
||||
return _.find(obj.attributes, filterNodeName(name)).nodeValue;
|
||||
}
|
||||
|
||||
function getTags(obj) {
|
||||
var tags = {};
|
||||
// Doesn't use underscore for performance reasons
|
||||
for (var i = 0; i < obj.childNodes.length; i++) {
|
||||
var item = obj.childNodes[i];
|
||||
if (item.nodeName === 'tag') {
|
||||
tags[getAttribute(item,'k')] = getAttribute(item,'v');
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
function getNodes(obj) {
|
||||
var nodes = [];
|
||||
// Doesn't use underscore for performance reasons
|
||||
for (var i = 0; i < obj.childNodes.length; i++) {
|
||||
var item = obj.childNodes[i];
|
||||
if (item.nodeName === 'nd') {
|
||||
nodes.push(entities[getAttribute(item,'ref')]);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function getMembers(obj) {
|
||||
return _(obj.childNodes).chain()
|
||||
.filter(filterNodeName('member'))
|
||||
.map(function(item) {
|
||||
var id = getAttribute(item,'ref'),
|
||||
type = getAttribute(item,'type'),
|
||||
role = getAttribute(item,'role');
|
||||
|
||||
var obj = getOrCreate(id,type);
|
||||
return new iD.RelationMember(obj,role);
|
||||
}).value();
|
||||
}
|
||||
|
||||
function parse(callback) {
|
||||
return function(dom) {
|
||||
var nodelist = _.compact(_.map(dom.childNodes[0].childNodes, function(obj) {
|
||||
for (var i = 0; i < dom.childNodes[0].childNodes.length; i++) {
|
||||
var obj = dom.childNodes[0].childNodes[i];
|
||||
if (obj.nodeName === 'node') {
|
||||
var node = new iD.Node(connection,
|
||||
+getAttribute(obj, 'id'),
|
||||
@@ -135,7 +144,6 @@ iD.Connection = function(apiURL) {
|
||||
+getAttribute(obj, 'lon'),
|
||||
getTags(obj));
|
||||
assign(node);
|
||||
return node;
|
||||
} else if (obj.nodeName === 'way') {
|
||||
var way = new iD.Way(connection,
|
||||
getAttribute(obj, 'id'),
|
||||
@@ -149,51 +157,13 @@ iD.Connection = function(apiURL) {
|
||||
getTags(obj));
|
||||
assign(relation);
|
||||
}
|
||||
}));
|
||||
updatePOIs(nodelist);
|
||||
if (callback) { callback(nodelist); }
|
||||
|
||||
// Private functions to parse DOM created from XML file
|
||||
function filterNodeName(n) {
|
||||
return function(item) { return item.nodeName === n; };
|
||||
}
|
||||
|
||||
function getAttribute(obj, name) {
|
||||
return _.find(obj.attributes, filterNodeName(name)).nodeValue;
|
||||
}
|
||||
|
||||
function getTags(obj) {
|
||||
return _(obj.childNodes).chain()
|
||||
.filter(filterNodeName('tag'))
|
||||
.map(function(item) {
|
||||
return [getAttribute(item,'k'), getAttribute(item,'v')];
|
||||
}).object().value();
|
||||
}
|
||||
|
||||
function getNodes(obj) {
|
||||
return _(obj.childNodes).chain()
|
||||
.filter(filterNodeName('nd'))
|
||||
.map(function(item) {
|
||||
return entities[getAttribute(item,'ref')];
|
||||
}).value();
|
||||
}
|
||||
|
||||
function getMembers(obj) {
|
||||
return _(obj.childNodes).chain()
|
||||
.filter(filterNodeName('member'))
|
||||
.map(function(item) {
|
||||
var id = getAttribute(item,'ref'),
|
||||
type = getAttribute(item,'type'),
|
||||
role = getAttribute(item,'role');
|
||||
|
||||
var obj = getOrCreate(id,type);
|
||||
return new iD.RelationMember(obj,role);
|
||||
}).value();
|
||||
}
|
||||
callback();
|
||||
};
|
||||
}
|
||||
|
||||
connection.entities = entities;
|
||||
connection.all = all;
|
||||
connection.relations = relations;
|
||||
connection.loadFromAPI = loadFromAPI;
|
||||
connection.loadFromURL = loadFromURL;
|
||||
@@ -201,9 +171,6 @@ iD.Connection = function(apiURL) {
|
||||
connection.doCreateNode = doCreateNode;
|
||||
connection.doCreateWay = doCreateWay;
|
||||
connection.doCreateRelation = doCreateRelation;
|
||||
connection.registerPOI = registerPOI;
|
||||
connection.unregisterPOI = unregisterPOI;
|
||||
connection.getPOIs = getPOIs;
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
@@ -1,54 +1,19 @@
|
||||
define(['dojo/_base/declare','dojo/on','dojo/Evented',
|
||||
'iD/actions/UndoStack','iD/tags/PresetList'], function(declare,on,Evented){
|
||||
if (typeof iD === 'undefined') iD = {};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Controller base class
|
||||
iD.Controller = function() {
|
||||
var controller = {},
|
||||
state = null;
|
||||
|
||||
declare("iD.Controller", [Evented], {
|
||||
state: null, // current ControllerState
|
||||
map: null, // current Map
|
||||
stepper: null, // current StepPane
|
||||
undoStack: null, // main undoStack
|
||||
editorCache: null, // cache of tag editor objects, means we don't have to repeatedly load them
|
||||
|
||||
constructor:function(map) {
|
||||
// summary: The Controller marshalls ControllerStates and passes events to them.
|
||||
this.map = map;
|
||||
this.undoStack = new iD.UndoStack();
|
||||
this.editorCache = {};
|
||||
},
|
||||
controller.undoStack = new iD.UndoStack();
|
||||
|
||||
setStepper: function(stepper) {
|
||||
// summary: Set reference for the singleton-like class for the step-by-step instruction panel.
|
||||
this.stepper = stepper;
|
||||
},
|
||||
controller.setState = function(newState) {
|
||||
// summary: Enter a new ControllerState, firing exitState on the old one, and enterState on the new one.
|
||||
if (newState === state) { return; }
|
||||
if (state) state.exitState();
|
||||
newState.controller = controller;
|
||||
state = newState;
|
||||
newState.enterState();
|
||||
};
|
||||
|
||||
setState:function(newState) {
|
||||
// summary: Enter a new ControllerState, firing exitState on the old one, and enterState on the new one.
|
||||
if (newState === this.state) { return; }
|
||||
if (this.state) {
|
||||
this.emit("exitState", {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
}
|
||||
newState.controller = this;
|
||||
this.state = newState;
|
||||
newState.enterState();
|
||||
this.emit("enterState", {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
},
|
||||
|
||||
entityMouseEvent:function(event,entityUI) {
|
||||
// summary: Pass a MouseEvent on an EntityUI (e.g. clicking a way) to the current ControllerState.
|
||||
if (!this.state) { return; }
|
||||
var newState = this.state.processMouseEvent(event,entityUI);
|
||||
this.setState(newState);
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
return controller;
|
||||
};
|
||||
|
||||
@@ -9,19 +9,15 @@ iD.Node = function(connection, id, lat, lon, tags, loaded) {
|
||||
this.entity = new iD.Entity();
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
// TODO: keep or trash this custom
|
||||
this[0] = lon;
|
||||
this[1] = lat;
|
||||
this.tags = tags;
|
||||
this.loaded = (loaded === undefined) ? true : loaded;
|
||||
this.project();
|
||||
this.modified = this.id < 0;
|
||||
};
|
||||
|
||||
iD.Node.prototype = {
|
||||
project: function() {
|
||||
// summary: Update the projected latitude value (this.latp) from the latitude (this.lat).
|
||||
this.latp = 180/Math.PI *
|
||||
Math.log(Math.tan(Math.PI/4+this.lat*(Math.PI/180)/2));
|
||||
},
|
||||
|
||||
toGeoJSON: function() {
|
||||
return {
|
||||
type: 'Feature',
|
||||
@@ -33,40 +29,10 @@ iD.Node.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
latp2lat: function(a) {
|
||||
// summary: Get a latitude from a projected latitude.
|
||||
// returns: Latitude.
|
||||
return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2);
|
||||
},
|
||||
|
||||
within: function(extent) {
|
||||
return (this.lon >= extent.west) &&
|
||||
(this.lon <= extent.east) &&
|
||||
(this.lat >= extent.south) &&
|
||||
(this.lat <= extent.north);
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
var ways = this.parentWays();
|
||||
_.each(ways, _.bind(function(way) { this.connection.refreshEntity(way); }, this));
|
||||
this.connection.refreshEntity(this);
|
||||
},
|
||||
|
||||
doSetLonLatp: function(lon, latproj, performAction) {
|
||||
// summary: Change the position of a node, using an undo stack.
|
||||
performAction(new iD.actions.MoveNodeAction(this,
|
||||
this.latp2lat(latproj),
|
||||
lon,
|
||||
_.bind(this._setLatLonImmediate, this)));
|
||||
},
|
||||
|
||||
_setLatLonImmediate: function(lat,lon) {
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.project();
|
||||
var ways = this.parentWays();
|
||||
_.each(ways, _.bind(function(way) {
|
||||
way.expandBbox(this);
|
||||
}, this));
|
||||
return (this.lon >= extent[0][0]) &&
|
||||
(this.lon <= extent[1][0]) &&
|
||||
(this.lat <= extent[0][1]) &&
|
||||
(this.lat >= extent[1][1]);
|
||||
}
|
||||
};
|
||||
|
||||
25
js/iD/Way.js
25
js/iD/Way.js
@@ -16,7 +16,6 @@ iD.Way = function(connection, id, nodes, tags, loaded) {
|
||||
_.each(nodes, _.bind(function(node) {
|
||||
node.entity.addParent(this);
|
||||
}, this));
|
||||
this._calculateBbox();
|
||||
};
|
||||
|
||||
iD.Way.prototype = {
|
||||
@@ -46,9 +45,14 @@ iD.Way.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
bounds: function() {
|
||||
return d3.geo.bounds(this.toGeoJSON());
|
||||
},
|
||||
|
||||
// ---------------------
|
||||
// Bounding-box handling
|
||||
within: function(left,right,top,bottom) {
|
||||
var bounds = this.bounds();
|
||||
// TODO invert and just return
|
||||
if (!this.extent.west ||
|
||||
(this.extent.west < left && this.extent.east < left ) ||
|
||||
@@ -61,25 +65,6 @@ iD.Way.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_calculateBbox:function() {
|
||||
this.extent = {
|
||||
west: Infinity,
|
||||
east: -Infinity,
|
||||
south: Infinity,
|
||||
north: -Infinity
|
||||
};
|
||||
_.each(this.nodes, _.bind(function(n) { this.expandBbox(n); }, this));
|
||||
},
|
||||
|
||||
expandBbox:function(node) {
|
||||
// summary: Enlarge the way's bounding box to make sure it
|
||||
// includes the co-ordinates of a supplied node.
|
||||
this.extent.west = Math.min(this.extent.west, node.lon);
|
||||
this.extent.east = Math.max(this.extent.east, node.lon);
|
||||
this.extent.south = Math.min(this.extent.south, node.lat);
|
||||
this.extent.north = Math.max(this.extent.north, node.lat);
|
||||
},
|
||||
|
||||
// --------------
|
||||
// Action callers
|
||||
|
||||
|
||||
1
js/iD/controller/controller.js
Normal file
1
js/iD/controller/controller.js
Normal file
@@ -0,0 +1 @@
|
||||
iD.controller = {};
|
||||
@@ -1,13 +1,8 @@
|
||||
// iD/controller/edit/EditBaseState.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/array','dojo/on',
|
||||
'dijit/registry'
|
||||
], function(declare,lang,array,on,registry){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// EditBaseState class - provides shared UI functions to edit mode states
|
||||
|
||||
declare("iD.controller.edit.EditBaseState", null, {
|
||||
iD.controller.edit.EditBaseState = function() {};
|
||||
iD.controller.edit.EditBaseState.prototype = {
|
||||
|
||||
editortooltip: null,
|
||||
|
||||
@@ -73,8 +68,4 @@ declare("iD.controller.edit.EditBaseState", null, {
|
||||
// summary: Close the tooltip.
|
||||
$('.edit-pane').removeClass('active').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
// iD/controller/edit/NoSelection.js
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/controller/edit/EditBaseState',
|
||||
'iD/controller/edit/SelectedWay',
|
||||
'iD/controller/edit/SelectedWayNode',
|
||||
'iD/controller/edit/SelectedPOINode'
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// edit.NoSelection class
|
||||
|
||||
declare("iD.controller.edit.NoSelection", [iD.controller.edit.EditBaseState], {
|
||||
iD.controller.edit.NoSelection = function() {};
|
||||
iD.controller.edit.NoSelection.prototype = {
|
||||
|
||||
constructor: function() {
|
||||
// summary: In 'Edit object' mode but nothing selected.
|
||||
},
|
||||
|
||||
enterState: function() {
|
||||
this.controller.stepper.hide();
|
||||
// this.controller.stepper.hide();
|
||||
},
|
||||
|
||||
processMouseEvent: function(event, entityUI) {
|
||||
@@ -38,8 +30,4 @@ declare("iD.controller.edit.NoSelection", [iD.controller.edit.EditBaseState], {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,50 +1,43 @@
|
||||
// iD/controller/edit/SelectedPOINode.js
|
||||
// ----------------------------------------------------------------------
|
||||
// edit.SelectedPOINode class
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/edit/EditBaseState'], function(declare){
|
||||
iD.controller.edit.SelectedPOINode = function() {};
|
||||
iD.controller.edit.SelectedPOINode.prototype = {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// edit.SelectedPOINode class
|
||||
node: null,
|
||||
nodeUI: null,
|
||||
|
||||
declare("iD.controller.edit.SelectedPOINode", [iD.controller.edit.EditBaseState], {
|
||||
constructor: function(node) {
|
||||
// summary: In 'Edit object' mode and a POI node is selected.
|
||||
this.node = node;
|
||||
},
|
||||
|
||||
node: null,
|
||||
nodeUI: null,
|
||||
enterState: function() {
|
||||
var map = this.controller.map;
|
||||
this.nodeUI = map.getUI(this.node);
|
||||
this.nodeUI.setStateClass('selected')
|
||||
.redraw();
|
||||
this.openEditorTooltip(this.node);
|
||||
return this;
|
||||
},
|
||||
|
||||
constructor: function(node) {
|
||||
// summary: In 'Edit object' mode and a POI node is selected.
|
||||
this.node = node;
|
||||
},
|
||||
exitState: function() {
|
||||
this.nodeUI.resetStateClass('selected')
|
||||
.redraw();
|
||||
this.closeEditorTooltip();
|
||||
return this;
|
||||
},
|
||||
|
||||
enterState: function() {
|
||||
var map = this.controller.map;
|
||||
this.nodeUI = map.getUI(this.node);
|
||||
this.nodeUI.setStateClass('selected')
|
||||
.redraw();
|
||||
this.openEditorTooltip(this.node);
|
||||
return this;
|
||||
},
|
||||
|
||||
exitState: function() {
|
||||
this.nodeUI.resetStateClass('selected')
|
||||
.redraw();
|
||||
this.closeEditorTooltip();
|
||||
return this;
|
||||
},
|
||||
|
||||
processMouseEvent: function(event, entityUI) {
|
||||
if (event.type !== 'click') return this;
|
||||
var entity = entityUI ? entityUI.entity : null;
|
||||
var entityType = entity ? entity.entityType : null;
|
||||
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, event);
|
||||
}
|
||||
return this;
|
||||
processMouseEvent: function(event, entityUI) {
|
||||
if (event.type !== 'click') return this;
|
||||
var entity = entityUI ? entityUI.entity : null;
|
||||
var entityType = entity ? entity.entityType : null;
|
||||
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, event);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,65 +1,57 @@
|
||||
// iD/controller/edit/SelectedWay.js
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/edit/EditBaseState'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// edit.SelectedWay class
|
||||
|
||||
declare("iD.controller.edit.SelectedWay", [iD.controller.edit.EditBaseState], {
|
||||
iD.controller.edit.SelectedWay = function() {};
|
||||
iD.controller.edit.SelectedWay.prototype = {
|
||||
|
||||
way: null,
|
||||
wayUI: null,
|
||||
entryevent: null,
|
||||
way: null,
|
||||
wayUI: null,
|
||||
entryevent: null,
|
||||
|
||||
constructor:function(way, event) {
|
||||
// summary: In 'Edit object' mode and a way is selected.
|
||||
this.way = way;
|
||||
this.entryevent = event;
|
||||
},
|
||||
enterState:function() {
|
||||
this.wayUI = this.controller.map.getUI(this.way);
|
||||
this.wayUI.setStateClass('selected');
|
||||
this.wayUI.setStateClass('shownodes');
|
||||
if (this.entryevent) {
|
||||
constructor:function(way, event) {
|
||||
// summary: In 'Edit object' mode and a way is selected.
|
||||
this.way = way;
|
||||
this.entryevent = event;
|
||||
},
|
||||
enterState:function() {
|
||||
this.wayUI = this.controller.map.getUI(this.way);
|
||||
this.wayUI.setStateClass('selected');
|
||||
this.wayUI.setStateClass('shownodes');
|
||||
if (this.entryevent) {
|
||||
this.openEditorTooltip(this.entryevent.clientX, this.entryevent.clientY, this.way);
|
||||
}
|
||||
this.wayUI.redraw();
|
||||
},
|
||||
exitState:function() {
|
||||
this.wayUI.resetStateClass('selected');
|
||||
this.wayUI.resetStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
this.closeEditorTooltip();
|
||||
},
|
||||
|
||||
processMouseEvent:function(event, entityUI) {
|
||||
var entity = entityUI ? entityUI.entity : null;
|
||||
var entityType = entity ? entity.entityType : null;
|
||||
this.wayUI.redraw();
|
||||
},
|
||||
exitState:function() {
|
||||
this.wayUI.resetStateClass('selected');
|
||||
this.wayUI.resetStateClass('shownodes');
|
||||
this.wayUI.redraw();
|
||||
this.closeEditorTooltip();
|
||||
},
|
||||
|
||||
if (event.type === 'click') {
|
||||
switch (entityType) {
|
||||
case null:
|
||||
return new iD.controller.edit.NoSelection();
|
||||
case 'node':
|
||||
var ways = entity.entity.parentWays();
|
||||
if (entity.entity.hasParent(this.way)) {
|
||||
return new iD.controller.edit.SelectedWayNode(entity, this.way);
|
||||
} else if (!ways.length) {
|
||||
return new iD.controller.edit.SelectedPOINode(entity);
|
||||
} else {
|
||||
return new iD.controller.edit.SelectedWayNode(entity, ways[0]);
|
||||
}
|
||||
break;
|
||||
case 'way':
|
||||
return new iD.controller.edit.SelectedWay(entityUI.entity, event);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
processMouseEvent:function(event, entityUI) {
|
||||
var entity = entityUI ? entityUI.entity : null;
|
||||
var entityType = entity ? entity.entityType : null;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
if (event.type === 'click') {
|
||||
switch (entityType) {
|
||||
case null:
|
||||
return new iD.controller.edit.NoSelection();
|
||||
case 'node':
|
||||
var ways = entity.entity.parentWays();
|
||||
if (entity.entity.hasParent(this.way)) {
|
||||
return new iD.controller.edit.SelectedWayNode(entity, this.way);
|
||||
} else if (!ways.length) {
|
||||
return new iD.controller.edit.SelectedPOINode(entity);
|
||||
} else {
|
||||
return new iD.controller.edit.SelectedWayNode(entity, ways[0]);
|
||||
}
|
||||
break;
|
||||
case 'way':
|
||||
return new iD.controller.edit.SelectedWay(entityUI.entity, event);
|
||||
}
|
||||
} else { }
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
// iD/controller/edit/SelectedWayNode.js
|
||||
|
||||
define(['dojo/_base/declare','iD/controller/edit/EditBaseState'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWayNode class
|
||||
|
||||
declare("iD.controller.edit.SelectedWayNode", [iD.controller.edit.EditBaseState], {
|
||||
iD.controller.edit.SelectedWayNode = function() {};
|
||||
iD.controller.edit.SelectedWayNode.prototype = {
|
||||
|
||||
node: null,
|
||||
way: null,
|
||||
@@ -53,8 +49,4 @@ declare("iD.controller.edit.SelectedWayNode", [iD.controller.edit.EditBaseState]
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
};
|
||||
|
||||
1
js/iD/controller/edit/edit.js
Normal file
1
js/iD/controller/edit/edit.js
Normal file
@@ -0,0 +1 @@
|
||||
iD.controller.edit = {};
|
||||
@@ -1,134 +0,0 @@
|
||||
// iD/renderer/EntityUI.js
|
||||
// EntityUI classes for iD
|
||||
// **** TODO:
|
||||
// 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','iD/Entity','iD/renderer/Map'],
|
||||
function(declare) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 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) {
|
||||
// summary: Base class for a UI representing an entity.
|
||||
this.entity=entity;
|
||||
this.map=map;
|
||||
this.stateClasses=stateClasses ? stateClasses.slice() : [];
|
||||
this.sprites=[];
|
||||
},
|
||||
getConnection: function() {
|
||||
// summary: Get the Connection from where the map draws its data.
|
||||
return this.map.connection; // iD.Connection
|
||||
},
|
||||
targetGroup: function(groupType,sublayer) {
|
||||
// summary: Find a gfx.Group to render on.
|
||||
return this.map.sublayer(this.layer,groupType,sublayer); // dojox.gfx.Group
|
||||
},
|
||||
recordSprite: function(sprite) {
|
||||
// summary: Record that an individual sprite (one stroke, icon or text item) has been added.
|
||||
if (!_.include(this.sprites, sprite)) {
|
||||
this.sprites.push(sprite);
|
||||
}
|
||||
return sprite;
|
||||
},
|
||||
removeSprites: function() {
|
||||
// summary: Clear all sprites currently used.
|
||||
for (var i=0; i<this.sprites.length; i++) {
|
||||
this.sprites[i].removeShape();
|
||||
}
|
||||
this.sprites=[];
|
||||
},
|
||||
refreshStyleList: function(tags) {
|
||||
// summary: Calculate the list of styles that apply to this UI at this zoom level.
|
||||
if (!this.styleList || !this.styleList.isValidAt(this.map.zoom)) {
|
||||
this.styleList=this.map.ruleset.getStyles(this.entity,tags, this.map.zoom);
|
||||
}
|
||||
this.layer=this.styleList.layerOverride();
|
||||
if (isNaN(this.layer)) {
|
||||
this.layer=0;
|
||||
if (tags.layer) { this.layer = +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() {
|
||||
// summary: Return tags for this entity augmented by the EntityUI's state classes.
|
||||
var tags = _.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; // Object
|
||||
},
|
||||
|
||||
// --------------------
|
||||
// State class handling
|
||||
|
||||
setStateClasses:function(stateClasses) {
|
||||
// summary: Set all state classes at once, and prompt a redraw if they're different to previously,
|
||||
if (stateClasses && this.stateClasses.join(',')!=stateClasses.join(',')) {
|
||||
this.stateClasses=stateClasses.slice();
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setStateClass:function(sc) {
|
||||
// summary: Set a single state class, and prompt a redraw if it wasn't set previously.
|
||||
if (this.stateClasses.indexOf(sc)==-1) {
|
||||
this.stateClasses.push(sc);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
resetStateClass:function(sc) {
|
||||
// summary: Reset a single state class, and prompt a redraw if it was set previously.
|
||||
if (this.stateClasses.indexOf(sc)>-1) {
|
||||
this.stateClasses.splice(this.stateClasses.indexOf(sc),1);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
hasStateClass:function(sc) {
|
||||
// summary: Is a particular state class set for this UI?
|
||||
return this.stateClasses.indexOf(sc) > -1;
|
||||
},
|
||||
|
||||
invalidateStyleList:function() {
|
||||
// summary: Invalidate the StyleList so it's recalculated on next redraw.
|
||||
this.styleList = null;
|
||||
},
|
||||
|
||||
// --------------------
|
||||
// Mouse event handling
|
||||
|
||||
entityMouseEvent:function(event) {
|
||||
// summary: Receive a mouse event (e.g. clicking on the UI), and forward it to the Controller.
|
||||
this.map.controller.entityMouseEvent(event, event.gfxTarget.source);
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
@@ -1,514 +1,389 @@
|
||||
// iD/renderer/Map.js
|
||||
// at present this combines P2's Map and MapPaint functionality
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/event',
|
||||
'dojo/dom-geometry',
|
||||
'dojox/gfx','dojox/gfx/matrix',
|
||||
'iD/Connection','iD/Entity','iD/renderer/EntityUI','iD/renderer/WayUI','iD/renderer/NodeUI'],
|
||||
function(declare, Event, domGeom, Gfx, Matrix){
|
||||
// ----------------------------------------------------------------------
|
||||
// Connection base class
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Connection base class
|
||||
iD.renderer.Map = function(obj) {
|
||||
var map = {},
|
||||
selection = [],
|
||||
width = obj.width || 800,
|
||||
height = obj.height || 400,
|
||||
controller = iD.Controller(),
|
||||
projection = d3.geo.mercator()
|
||||
.scale(512).translate([512, 512]),
|
||||
connection = obj.connection,
|
||||
layers = {};
|
||||
|
||||
declare("iD.renderer.Map", null, {
|
||||
var tagclasses = [
|
||||
'highway', 'railway', 'motorway', 'amenity', 'landuse', 'building', 'bridge'];
|
||||
|
||||
MASTERSCALE: 5825.4222222222,
|
||||
MINSCALE: 14,
|
||||
MAXSCALE: 23,
|
||||
zoom: NaN,
|
||||
zoomfactor: 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
|
||||
var linegen = d3.svg.line()
|
||||
.x(function(d) { return projection(d)[0]; })
|
||||
.y(function(d) { return projection(d)[1]; });
|
||||
|
||||
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)
|
||||
connection: null, // data store
|
||||
controller: null, // UI controller
|
||||
uis: {},
|
||||
var zoombehavior = d3.behavior.zoom()
|
||||
.translate(projection.translate())
|
||||
.scale(projection.scale())
|
||||
.scaleExtent([256, 134217728]);
|
||||
|
||||
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
|
||||
zoombehavior.on('zoom', redraw);
|
||||
|
||||
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
|
||||
var surface = d3.selectAll(obj.selector)
|
||||
.append('svg')
|
||||
.attr({ width: width, height: width })
|
||||
.call(zoombehavior);
|
||||
|
||||
containerx: 0, // screen co-ordinates of container
|
||||
containery: 0, // |
|
||||
centrelat: NaN, // lat/long and bounding box of map
|
||||
centrelon: NaN, // |
|
||||
extent: {}, // |
|
||||
mapheight: NaN, // size of map object in pixels
|
||||
mapwidth: NaN, // |
|
||||
var defs = surface.append('defs');
|
||||
|
||||
layers: null, // array-like object of Groups, one for each OSM layer
|
||||
minlayer: -5, // minimum OSM layer supported
|
||||
maxlayer: 5, // maximum OSM layer supported
|
||||
var clipPath = defs.append('clipPath')
|
||||
.attr('id', 'clip')
|
||||
.append('rect')
|
||||
.attr('id', 'clip-rect')
|
||||
.attr({ x: 0, y: 0 })
|
||||
.attr({ width: width, height: height });
|
||||
|
||||
elastic: null, // Group for drawing elastic band
|
||||
var tilegroup = surface.append('g')
|
||||
.attr('clip-path', 'url(#clip)'),
|
||||
container = surface.append('g')
|
||||
.attr('clip-path', 'url(#clip)');
|
||||
|
||||
ruleset: null, // map style
|
||||
var r = container.append('g');
|
||||
|
||||
constructor: function(obj) {
|
||||
// summary: The main map display, containing the individual sprites (UIs) for each entity.
|
||||
// obj: Object An object containing .lat, .lon, .scale, .div (the name of the <div> to be used),
|
||||
// .connection, .width (px) and .height (px) properties.
|
||||
layers[0] = {
|
||||
root: r,
|
||||
fill: r.append('g'),
|
||||
casing: r.append('g'),
|
||||
stroke: r.append('g'),
|
||||
text: r.append('g'),
|
||||
hit: r.append('g')
|
||||
};
|
||||
|
||||
this.mapwidth = obj.width ? obj.width : 800;
|
||||
this.mapheight = obj.height ? obj.height : 400;
|
||||
var elastic = container.append('g');
|
||||
|
||||
// Initialise variables
|
||||
this.uis = {};
|
||||
this.div=document.getElementById(obj.div);
|
||||
this.surface=Gfx.createSurface(obj.div, 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.connection = obj.connection;
|
||||
this.zoom = obj.zoom ? obj.zoom : 17;
|
||||
this.baselon = obj.lon;
|
||||
this.baselat = obj.lat;
|
||||
this.baselatp = this.lat2latp(obj.lat);
|
||||
this._setScaleFactor();
|
||||
this.updateCoordsFromViewportPosition();
|
||||
var download = _.debounce(function() {
|
||||
connection.loadFromAPI(extent(), drawVector);
|
||||
}, 1000);
|
||||
|
||||
// Cache the margin box, since this is expensive.
|
||||
this.marginBox = domGeom.getMarginBox(this.div);
|
||||
function extent() {
|
||||
return [
|
||||
projection.invert([0, 0]),
|
||||
projection.invert([width, height])];
|
||||
}
|
||||
|
||||
// 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()
|
||||
};
|
||||
function select(d) {
|
||||
selection = [d._id];
|
||||
}
|
||||
|
||||
function key(d) { return d._id; }
|
||||
|
||||
function classes(pre) {
|
||||
return function(d) {
|
||||
var tags = d.tags;
|
||||
var c = [pre];
|
||||
function clean(x) {
|
||||
return tagclasses.indexOf(x) !== -1;
|
||||
}
|
||||
|
||||
// Create group for elastic band
|
||||
this.elastic = this.container.createGroup();
|
||||
|
||||
// Make draggable
|
||||
this.backdrop.connect("onmousedown", _.bind(this.startDrag, this));
|
||||
this.tilegroup.connect("onmousedown", _.bind(this.startDrag, this));
|
||||
this.surface.connect("onclick", _.bind(this.clickSurface, this));
|
||||
this.surface.connect("onmousemove", _.bind(this.processMove, this));
|
||||
this.surface.connect("onmousedown", _.bind(this._mouseEvent, this));
|
||||
this.surface.connect("onmouseup", _.bind(this._mouseEvent, this));
|
||||
},
|
||||
|
||||
setController:function(controller) {
|
||||
// summary: Set the controller that will handle events on the map (e.g. mouse clicks).
|
||||
this.controller = controller;
|
||||
},
|
||||
|
||||
_moveToPosition:function(group, position) {
|
||||
// summary: Supplementary method for dojox.gfx.
|
||||
// This should ideally be core Dojo stuff: see http://bugs.dojotoolkit.org/ticket/15296
|
||||
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]);
|
||||
for (var k in tags) {
|
||||
if (!clean(k)) continue;
|
||||
c.push(k + '-' + tags[k]);
|
||||
c.push(k);
|
||||
}
|
||||
},
|
||||
|
||||
_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;
|
||||
}
|
||||
if (selection.indexOf(d._id) !== -1) {
|
||||
c.push('active');
|
||||
}
|
||||
},
|
||||
return c.join(' ');
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Sprite and EntityUI handling
|
||||
var icons = {
|
||||
tourism: ['hotel'],
|
||||
shop: [
|
||||
'convenience',
|
||||
'supermarket'],
|
||||
amenity:
|
||||
[
|
||||
'atm',
|
||||
'bank',
|
||||
'cafe',
|
||||
'pub',
|
||||
'place',
|
||||
'parking',
|
||||
'bicycle_parking',
|
||||
'pharmacy',
|
||||
'pharmacy',
|
||||
'police',
|
||||
'post_box',
|
||||
'recycling',
|
||||
'restaurant',
|
||||
'school',
|
||||
'taxi',
|
||||
'telephone']
|
||||
};
|
||||
|
||||
sublayer:function(layer,groupType,sublayer) {
|
||||
// summary: Find the gfx.Group for a given OSM layer and rendering sublayer, creating it
|
||||
// if necessary. Note that sublayers are only implemented for stroke and fill.
|
||||
// groupType: String 'casing','text','hit','stroke', or 'fill'
|
||||
var collection = this.layers[layer][groupType], sub;
|
||||
switch (groupType) {
|
||||
case 'casing':
|
||||
case 'text':
|
||||
case 'hit':
|
||||
return collection;
|
||||
function markerimage(d) {
|
||||
for (var k in icons) {
|
||||
if (d.tags[k] && icons[k].indexOf(d.tags[k]) !== -1) {
|
||||
return 'icons/' + d.tags[k] + '.png';
|
||||
}
|
||||
// Find correct sublayer, inserting if necessary
|
||||
var insertAt=collection.children.length;
|
||||
for (var i = 0; i < collection.children.length; i++) {
|
||||
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; // dojox.gfx.Group
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
createUI: function(e, stateClasses) {
|
||||
// summary: Create a UI (sprite) for an entity, assigning any specified state classes
|
||||
// (temporary attributes such as ':hover' or ':selected')
|
||||
if (!this.uis[e.id]) {
|
||||
if (e.entityType === 'node') {
|
||||
this.uis[e.id] = new iD.renderer.NodeUI(e, this, stateClasses);
|
||||
} else if (e.entityType === 'way') {
|
||||
this.uis[e.id] = new iD.renderer.WayUI(e, this, stateClasses);
|
||||
}
|
||||
} else {
|
||||
this.uis[e.id].setStateClasses(stateClasses).redraw();
|
||||
}
|
||||
},
|
||||
function selectClick(d) {
|
||||
select(d);
|
||||
drawVector();
|
||||
}
|
||||
|
||||
getUI: function(e) {
|
||||
// summary: Return the UI for an entity, if it exists.
|
||||
return this.uis[e.id]; // iD.renderer.EntityUI
|
||||
},
|
||||
function nodeline(d) {
|
||||
return linegen(d.nodes);
|
||||
}
|
||||
|
||||
refreshUI: function(e) {
|
||||
// summary: Redraw the UI for an entity.
|
||||
if (this.uis[e.id]) { this.uis[e.id].redraw(); }
|
||||
},
|
||||
var highway_stack = [
|
||||
'motorway',
|
||||
'motorway_link',
|
||||
'trunk',
|
||||
'trunk_link',
|
||||
'primary',
|
||||
'primary_link',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
'unclassified',
|
||||
'residential',
|
||||
'service',
|
||||
'footway'
|
||||
];
|
||||
|
||||
deleteUI: function(e) {
|
||||
// summary: Delete the UI for an entity.
|
||||
if (this.uis[e.id]) {
|
||||
this.uis[e.id].removeSprites();
|
||||
delete this.uis[e.id];
|
||||
}
|
||||
},
|
||||
function waystack(a, b) {
|
||||
if (!a || !b) return 0;
|
||||
if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
|
||||
return a.tags.layer - b.tags.layer;
|
||||
}
|
||||
if (a.tags.bridge) return 1;
|
||||
if (b.tags.bridge) return -1;
|
||||
var as = 0, bs = 0;
|
||||
if (a.tags.highway && b.tags.highway) {
|
||||
as -= highway_stack.indexOf(a.tags.highway);
|
||||
bs -= highway_stack.indexOf(b.tags.highway);
|
||||
}
|
||||
return as - bs;
|
||||
}
|
||||
|
||||
download: function() {
|
||||
// summary: Ask the connection to download data for the current viewport.
|
||||
$('#progress').show().addClass('spinner');
|
||||
this.connection.loadFromAPI(this.extent, _.bind(this.updateUIs, this));
|
||||
},
|
||||
function drawVector() {
|
||||
var all = connection.all();
|
||||
|
||||
updateUIs: function() {
|
||||
// summary: Draw/refresh all EntityUIs within the bbox, and remove any others.
|
||||
// redraw: Boolean Should we redraw any UIs that are already present?
|
||||
// remove: Boolean Should we delete any UIs that are no longer in the bbox?
|
||||
$('#progress').hide().removeClass('spinner');
|
||||
var o = this.connection.getObjectsByBbox(this.extent);
|
||||
var touch = _(o.inside).chain()
|
||||
.filter(function(w) { return w.loaded; })
|
||||
.map(_.bind(function(e) {
|
||||
if (!this.uis[e.id]) {
|
||||
this.createUI(e);
|
||||
} else {
|
||||
this.uis[e.id].redraw();
|
||||
}
|
||||
return '' + e.id;
|
||||
}, this)).value();
|
||||
_.each(_.difference(_.keys(this.uis), touch), _.bind(function(k) {
|
||||
console.log(k);
|
||||
this.deleteUI(k);
|
||||
}, this));
|
||||
},
|
||||
|
||||
// -------------
|
||||
// Zoom handling
|
||||
|
||||
zoomIn: function() {
|
||||
// summary: Zoom in by one level (unless maximum reached).
|
||||
return this.setZoom(this.zoom + 1);
|
||||
},
|
||||
|
||||
zoomOut: function() {
|
||||
// summary: Zoom out by one level (unless minimum reached).
|
||||
this.setZoom(this.zoom - 1);
|
||||
this.download();
|
||||
return this;
|
||||
},
|
||||
|
||||
setZoom: function(zoom) {
|
||||
if (zoom < this.MINSCALE || zoom > this.MAXSCALE) return this;
|
||||
// summary: Redraw the map at a new zoom level.
|
||||
this.zoom = zoom;
|
||||
this._setScaleFactor();
|
||||
this._blankTiles();
|
||||
this.setCentre({
|
||||
lat: this.centrelat,
|
||||
lon: this.centrelon
|
||||
var ways = all.filter(function(a) {
|
||||
return a.entityType === 'way' && !a.isClosed();
|
||||
}),
|
||||
areas = all.filter(function(a) {
|
||||
return a.entityType === 'way' && a.isClosed();
|
||||
}),
|
||||
points = all.filter(function(a) {
|
||||
return a.entityType === 'node';
|
||||
});
|
||||
this.updateUIs(true, true);
|
||||
return this;
|
||||
},
|
||||
|
||||
_setScaleFactor: function() {
|
||||
// summary: Calculate the scaling factor for this zoom level.
|
||||
this.zoomfactor = this.MASTERSCALE/Math.pow(2, 13 - this.zoom);
|
||||
},
|
||||
var defpaths = defs.selectAll('path')
|
||||
.data(ways, key),
|
||||
fills = layers[0].fill.selectAll('path.area')
|
||||
.data(areas, key),
|
||||
casings = layers[0].casing.selectAll('use.casing')
|
||||
.data(ways, key),
|
||||
strokes = layers[0].stroke.selectAll('use.stroke')
|
||||
.data(ways, key),
|
||||
texts = layers[0].text.selectAll('text')
|
||||
.data(ways.filter(function(w) {
|
||||
return !!w.tags.name;
|
||||
}), key),
|
||||
markers = layers[0].hit.selectAll('image.marker')
|
||||
.data(points, key);
|
||||
|
||||
// ----------------------
|
||||
// Elastic band redrawing
|
||||
var _id = selection[0];
|
||||
var active_entity = all.filter(function(a) {
|
||||
return a._id === _id;
|
||||
});
|
||||
|
||||
clearElastic: function() {
|
||||
// summary: Remove the elastic band used to draw new ways.
|
||||
this.elastic.clear();
|
||||
},
|
||||
var handles = layers[0].hit.selectAll('circle.handle')
|
||||
.data(active_entity.length ? active_entity[0].nodes : [], key);
|
||||
|
||||
drawElastic: function(x1,y1,x2,y2) {
|
||||
// summary: Draw the elastic band (for new ways) between two points.
|
||||
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
|
||||
defpaths.exit().remove();
|
||||
texts.exit().remove();
|
||||
handles.exit().remove();
|
||||
fills.exit().remove();
|
||||
markers.exit().remove();
|
||||
casings.exit().remove();
|
||||
strokes.exit().remove();
|
||||
|
||||
defpaths.enter().append('path');
|
||||
|
||||
defpaths.attr('d', nodeline)
|
||||
.attr('id', function(d) {
|
||||
return 'd' + d._id;
|
||||
});
|
||||
},
|
||||
|
||||
// -------------
|
||||
// Tile handling
|
||||
// ** FIXME: see docs
|
||||
loadTiles: function() {
|
||||
// summary: Load all tiles for the current viewport. This is a bare-bones function
|
||||
// at present: it needs configurable URLs (not just Bing), attribution/logo
|
||||
// support, and to be 'nudgable' (i.e. adjust the offset).
|
||||
var tl = this.locationCoord({
|
||||
lat: this.extent.north,
|
||||
lon: this.extent.west
|
||||
}, this.zoom),
|
||||
br = this.locationCoord({
|
||||
lat: this.extent.south,
|
||||
lon: this.extent.east
|
||||
}, this.zoom),
|
||||
tileKeys = _.keys(this.tiles),
|
||||
seen = [],
|
||||
coord = { z: this.zoom };
|
||||
function usehref(d) {
|
||||
return '#d' + d._id;
|
||||
}
|
||||
|
||||
for (coord.x = tl.x; coord.x <= br.x; coord.x++) {
|
||||
for (coord.y = tl.y; coord.y <= br.y; coord.y++) {
|
||||
if (!this._getTile(coord)) {
|
||||
this._fetchTile(coord);
|
||||
}
|
||||
seen.push(iD.Util.tileKey(coord));
|
||||
}
|
||||
}
|
||||
fills.enter().append('path')
|
||||
.on('click', selectClick);
|
||||
|
||||
_.each(_.without(tileKeys, seen), _.bind(function(key) {
|
||||
delete this.tiles[key];
|
||||
}, this));
|
||||
},
|
||||
fills.attr('d', nodeline)
|
||||
.attr('class', classes('area'));
|
||||
|
||||
_fetchTile: function(coord) {
|
||||
// summary: Load a tile image at the given tile co-ordinates.
|
||||
var t = this.tilegroup.createImage({
|
||||
x: Math.floor(this.lon2coord(this.tile2lon(coord.x))),
|
||||
y: Math.floor(this.lat2coord(this.tile2lat(coord.y))),
|
||||
width: 256,
|
||||
height: 256,
|
||||
src: this._tileURL(coord)
|
||||
casings.enter().append('use');
|
||||
casings.sort(waystack)
|
||||
.attr('xlink:href', usehref)
|
||||
.attr('class', classes('casing'));
|
||||
|
||||
strokes.enter().append('use')
|
||||
.on('click', selectClick);
|
||||
|
||||
strokes.sort(waystack).attr('xlink:href', usehref)
|
||||
.attr('class', classes('stroke'));
|
||||
|
||||
markers.enter().append('image');
|
||||
markers.attr('class', classes('marker'))
|
||||
.attr({ width: 16, height: 16 })
|
||||
.attr('xlink:href', markerimage)
|
||||
.attr('transform', function(d) {
|
||||
return 'translate(' + projection(d) + ')';
|
||||
});
|
||||
this._assignTile(coord, t);
|
||||
},
|
||||
|
||||
_getTile: function(coord) {
|
||||
// summary: See if this tile is already loaded.
|
||||
return this.tiles[iD.Util.tileKey(coord)];
|
||||
},
|
||||
var textems = texts.enter().append('text')
|
||||
.attr('dy', 3);
|
||||
|
||||
_assignTile: function(coord, t) {
|
||||
// summary: Store a reference to the tile so we know it's loaded.
|
||||
this.tiles[iD.Util.tileKey(coord)] = t;
|
||||
},
|
||||
textems.append('textPath')
|
||||
.attr('xlink:href', usehref)
|
||||
.attr('startOffset', '50%')
|
||||
.text(function(d) { return d.tags.name; });
|
||||
|
||||
_tileURL: function(coord) {
|
||||
// summary: Calculate the URL for a tile at the given co-ordinates.
|
||||
var u = '';
|
||||
for (var zoom = coord.z; zoom > 0; zoom--) {
|
||||
var byte = 0;
|
||||
var mask = 1 << (zoom - 1);
|
||||
if ((coord.x & mask) !== 0) byte++;
|
||||
if ((coord.y & mask) !== 0) byte += 2;
|
||||
u += byte.toString();
|
||||
}
|
||||
return this.tilebaseURL
|
||||
.replace('$z', coord.z)
|
||||
.replace('$x', coord.x)
|
||||
.replace('$y', coord.y)
|
||||
.replace('$quadkey', u);
|
||||
},
|
||||
handles.enter().append('circle')
|
||||
.attr('class', 'handle')
|
||||
.attr('r', 5)
|
||||
.on('click', selectClick);
|
||||
handles.attr('transform', function(d) {
|
||||
return 'translate(' + projection(d) + ')';
|
||||
});
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Zoom handling
|
||||
function zoomIn() {
|
||||
// summary: Zoom in by one level (unless maximum reached).
|
||||
return setZoom(getZoom() + 1);
|
||||
}
|
||||
|
||||
_blankTiles: function() {
|
||||
// summary: Unload all tiles and remove from the display.
|
||||
this.tilegroup.clear();
|
||||
this.tiles = {};
|
||||
},
|
||||
function zoomOut() {
|
||||
// summary: Zoom out by one level (unless minimum reached).
|
||||
return setZoom(getZoom() - 1);
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Co-ordinate management, dragging and redraw
|
||||
function getZoom(zoom) {
|
||||
var s = projection.scale();
|
||||
return Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
}
|
||||
|
||||
startDrag: function(e) {
|
||||
// summary: Start dragging the map in response to a mouse-down.
|
||||
// e: MouseEvent The mouse-down event that triggered it.
|
||||
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", _.bind(this.endDrag, this));
|
||||
},
|
||||
function setZoom(zoom) {
|
||||
// summary: Redraw the map at a new zoom level.
|
||||
projection.scale(256 * Math.pow(2, zoom - 1));
|
||||
zoombehavior.scale(projection.scale());
|
||||
drawVector();
|
||||
redraw();
|
||||
return map;
|
||||
}
|
||||
|
||||
endDrag: function(e) {
|
||||
// summary: Stop dragging the map in response to a mouse-up.
|
||||
// e: MouseEvent The mouse-up event that triggered it.
|
||||
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();
|
||||
},
|
||||
function tilesForView() {
|
||||
var t = projection.translate(),
|
||||
s = projection.scale(),
|
||||
z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
rz = Math.floor(z),
|
||||
ts = 256 * Math.pow(2, z - rz);
|
||||
|
||||
processMove: function(e) {
|
||||
// summary: Drag the map to a new origin.
|
||||
// e: MouseEvent The mouse-move event that triggered it.
|
||||
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);
|
||||
}
|
||||
},
|
||||
// This is the 0, 0 px of the projection
|
||||
var tile_origin = [s / 2 - t[0], s / 2 - t[1]],
|
||||
coords = [],
|
||||
cols = d3.range(Math.max(0, Math.floor((tile_origin[0] - width) / ts)),
|
||||
Math.max(0, Math.ceil((tile_origin[0] + width) / ts))),
|
||||
rows = d3.range(Math.max(0, Math.floor((tile_origin[1] - height) / ts)),
|
||||
Math.max(0, Math.ceil((tile_origin[1] + height) / ts)));
|
||||
|
||||
updateOrigin: function() {
|
||||
// summary: Tell Dojo to update the viewport origin.
|
||||
this.container.setTransform([Matrix.translate(this.containerx, this.containery)]);
|
||||
this.tilegroup.setTransform([Matrix.translate(this.containerx, this.containery)]);
|
||||
},
|
||||
cols.forEach(function(x) {
|
||||
rows.forEach(function(y) { coords.push([Math.floor(z), x, y]); });
|
||||
});
|
||||
return coords;
|
||||
}
|
||||
|
||||
_mouseEvent: function(e) {
|
||||
// summary: Catch mouse events on the surface but not the tiles - in other words,
|
||||
// on drawn items that don't have their own hitzones, like the fill of a shape.
|
||||
if (e.type=='mousedown') { this.startDrag(e); }
|
||||
// ** FIXME: we may want to reinstate this at some point...
|
||||
// this.controller.entityMouseEvent(e,null);
|
||||
},
|
||||
|
||||
updateCoordsFromViewportPosition: function(e) {
|
||||
// summary: Update centre and bbox from the current viewport origin.
|
||||
this._updateCoords(this.containerx, this.containery);
|
||||
},
|
||||
function tileUrl(coord) {
|
||||
var tmpl = 'http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z';
|
||||
var u = '';
|
||||
for (var zoom = coord[0]; zoom > 0; zoom--) {
|
||||
var byte = 0;
|
||||
var mask = 1 << (zoom - 1);
|
||||
if ((coord[1] & mask) !== 0) byte++;
|
||||
if ((coord[2] & mask) !== 0) byte += 2;
|
||||
u += byte.toString();
|
||||
}
|
||||
return tmpl.replace('$quadkey', u);
|
||||
}
|
||||
|
||||
setCentre: function(loc) {
|
||||
// summary: Update centre and bbox to a specified lat/lon.
|
||||
var coord = this.locationCoord(loc, this.zoom);
|
||||
this._updateCoords(
|
||||
-coord.x - this.mapwidth / 2,
|
||||
-coord.y - this.mapheight / 2);
|
||||
return this;
|
||||
},
|
||||
function redraw() {
|
||||
if (d3.event) {
|
||||
projection
|
||||
.translate(d3.event.translate)
|
||||
.scale(d3.event.scale);
|
||||
}
|
||||
|
||||
setCenter: function(loc) { this.setCentre(loc); },
|
||||
var t = projection.translate(),
|
||||
s = projection.scale(),
|
||||
z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
rz = Math.floor(z),
|
||||
ts = 256 * Math.pow(2, z - rz);
|
||||
|
||||
_updateCoords:function(x, y) {
|
||||
// summary: Set centre and bbox.
|
||||
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 is the 0, 0 px of the projection
|
||||
var tile_origin = [s / 2 - t[0], s / 2 - t[1]],
|
||||
coords = tilesForView();
|
||||
|
||||
this.extent = {
|
||||
north: this.coord2lat(-y),
|
||||
south: this.coord2lat(-y + this.mapheight),
|
||||
west: this.coord2lon(-x),
|
||||
east: this.coord2lon(-x + this.mapwidth)
|
||||
};
|
||||
var tiles = tilegroup.selectAll('image.tile')
|
||||
.data(coords, function(d) { return d.join(','); });
|
||||
|
||||
this.loadTiles();
|
||||
},
|
||||
tiles.exit().remove();
|
||||
tiles.enter().append('image')
|
||||
.attr('class', 'tile')
|
||||
.attr('xlink:href', tileUrl);
|
||||
tiles.attr({ width: ts, height: ts })
|
||||
.attr('transform', function(d) {
|
||||
return 'translate(' + [(d[1] * ts) - tile_origin[0], (d[2] * ts) - tile_origin[1]] + ')';
|
||||
});
|
||||
drawVector();
|
||||
download();
|
||||
}
|
||||
|
||||
clickSurface:function(e) {
|
||||
// summary: Handle a click on an empty area of the map.
|
||||
if (this.dragged && e.timeStamp==this.dragtime) { return; }
|
||||
this.controller.entityMouseEvent(e,null);
|
||||
},
|
||||
function setCentre(loc) {
|
||||
// summary: Update centre and bbox to a specified lat/lon.
|
||||
var t = projection.translate(),
|
||||
ll = projection([loc.lon, loc.lat]);
|
||||
projection.translate([
|
||||
t[0] - ll[0] + width / 2,
|
||||
t[1] - ll[1] + height / 2]);
|
||||
zoombehavior.translate(projection.translate());
|
||||
redraw();
|
||||
return map;
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Co-ordinate conversions
|
||||
map.download = download;
|
||||
map.extent = extent;
|
||||
map.setCentre = setCentre;
|
||||
map.setCenter = setCentre;
|
||||
|
||||
latp2coord:function(a) { return -(a-this.baselatp)*this.zoomfactor; },
|
||||
coord2latp:function(a) { return a/-this.zoomfactor+this.baselatp; },
|
||||
lon2coord:function(a) { return (a-this.baselon)*this.zoomfactor; },
|
||||
coord2lon:function(a) { return a/this.zoomfactor+this.baselon; },
|
||||
lon2screen:function(a) { return this.lon2coord(a) + this.marginBox.l + this.containerx; },
|
||||
map.getZoom = getZoom;
|
||||
map.setZoom = setZoom;
|
||||
map.zoomIn = zoomIn;
|
||||
map.zoomOut = zoomOut;
|
||||
|
||||
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.zoomfactor; },
|
||||
coord2lat:function(a) { return this.latp2lat(a/-this.zoomfactor+this.baselatp); },
|
||||
lat2screen:function(a) { return this.lat2coord(a) + this.marginBox.t + this.containery; },
|
||||
map.connection = connection;
|
||||
map.controller = controller;
|
||||
map.projection = projection;
|
||||
|
||||
locationCoord: function(ll, z) {
|
||||
var z2 = Math.pow(2, z), d2r = Math.PI / 180;
|
||||
return {
|
||||
z: z,
|
||||
x: Math.floor((ll.lon + 180) / 360 * z2),
|
||||
y: Math.floor((1 - Math.log(Math.tan(ll.lat * d2r) +
|
||||
1 / Math.cos(ll.lat * d2r)) / Math.PI) / 2 * z2)
|
||||
};
|
||||
},
|
||||
lon2tile:function(a) { return (Math.floor((a+180)/360*Math.pow(2,this.zoom))); },
|
||||
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.zoom))); },
|
||||
tile2lon:function(a) { return (a/Math.pow(2,this.zoom)*360-180); },
|
||||
tile2lat:function(a) {
|
||||
var n=Math.PI-2*Math.PI*a/Math.pow(2,this.zoom);
|
||||
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 - this.marginBox.l - this.containerx; },
|
||||
mouseY: function(e) { return e.clientY - this.marginBox.t - this.containery; }
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
redraw();
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
// iD/renderer/NodeUI.js
|
||||
// NodeUI classes for iD
|
||||
|
||||
define(['dojo/_base/declare','dojox/gfx/_base','iD/renderer/EntityUI'],
|
||||
function(declare, g) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// NodeUI class
|
||||
|
||||
declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
|
||||
constructor:function() {
|
||||
// summary: A UI (rendering) representing a node.
|
||||
this.redraw();
|
||||
},
|
||||
getEnhancedTags:function() {
|
||||
var tags=this.inherited(arguments);
|
||||
if (!this.entity.entity.hasParentWays()) { tags[':poi']='yes'; }
|
||||
// add junction and dupe
|
||||
return tags;
|
||||
},
|
||||
redraw:function() {
|
||||
// summary: Draw the object (mostly icons) and add hitzone sprites.
|
||||
var node = this.entity;
|
||||
this.removeSprites();
|
||||
|
||||
// Tags, position and styleList
|
||||
var x = Math.floor(this.map.lon2coord(this.entity.lon));
|
||||
var y = Math.floor(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;
|
||||
if (p.icon_image === 'square') shape = this.targetGroup('stroke', p.sublayer)
|
||||
.createRect({
|
||||
x: x-w/2,
|
||||
y: y-h/2,
|
||||
width: w,
|
||||
height: h
|
||||
});
|
||||
else if (p.icon_image === 'circle') shape = this.targetGroup('stroke', p.sublayer)
|
||||
.createCircle({
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: w
|
||||
});
|
||||
else shape = this.targetGroup('stroke',p.sublayer)
|
||||
.createImage({
|
||||
width: w,
|
||||
height: h,
|
||||
x: x-w/2,
|
||||
y: y-h/2,
|
||||
src: p.icon_image
|
||||
});
|
||||
if (p.icon_image === 'square' || p.icon_image === 'circle') {
|
||||
shape.setStroke(s.shapeStrokeStyler()).setFill(s.shapeFillStyler());
|
||||
}
|
||||
this.recordSprite(shape);
|
||||
|
||||
// Add text label
|
||||
// Add hit-zone
|
||||
var hit;
|
||||
if (p.icon_image === 'circle') {
|
||||
hit = this.targetGroup('hit').createCircle({
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: w
|
||||
});
|
||||
} else {
|
||||
hit = this.targetGroup('hit').createRect({
|
||||
x: x-w/2,
|
||||
y: y-h/2,
|
||||
width: w,
|
||||
height: h
|
||||
});
|
||||
}
|
||||
hit.setFill([0,1,0,0]).setStroke({
|
||||
width:2,
|
||||
color:[0,0,0,0]
|
||||
});
|
||||
this.recordSprite(hit);
|
||||
hit.source = this;
|
||||
hit.connect("onclick", _.bind(this.entityMouseEvent, this));
|
||||
hit.connect("onmousedown", _.bind(this.entityMouseEvent, this));
|
||||
hit.connect("onmouseup", _.bind(this.entityMouseEvent, this));
|
||||
hit.connect("onmouseenter", _.bind(this.entityMouseEvent, this));
|
||||
hit.connect("onmouseleave", _.bind(this.entityMouseEvent, this));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
@@ -1,146 +0,0 @@
|
||||
// iD/renderer/WayUI.js
|
||||
// WayUI classes for iD
|
||||
// **** TODO:
|
||||
// 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','iD/renderer/EntityUI'], function(declare) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// WayUI class
|
||||
|
||||
declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
|
||||
constructor: function() {
|
||||
// summary: A UI (rendering) representing a way.
|
||||
this.redraw();
|
||||
},
|
||||
getEnhancedTags: function() {
|
||||
var tags = this.inherited(arguments);
|
||||
if (this.entity.isClosed()) { tags[':area']='yes'; }
|
||||
return tags;
|
||||
},
|
||||
recalculate: function() {
|
||||
// summary: Not yet implemented - calculate length/centrepoint of UI for use in rendering.
|
||||
// ** FIXME: todo
|
||||
},
|
||||
redraw: function() {
|
||||
// summary: Draw the object and add hitzone sprites.
|
||||
var way = this.entity,
|
||||
maxwidth = 4,
|
||||
i;
|
||||
|
||||
this.removeSprites();
|
||||
if (!way.nodes.length) { return; }
|
||||
|
||||
// Create tags and calculate styleList
|
||||
var tags = this.getEnhancedTags();
|
||||
this.refreshStyleList(tags);
|
||||
|
||||
// List of co-ordinates
|
||||
var coords = _.map(way.nodes, _.bind(function(node) {
|
||||
return {
|
||||
x: this.map.lon2coord(node.lon),
|
||||
y: this.map.latp2coord(node.latp)
|
||||
};
|
||||
}, this));
|
||||
|
||||
// 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], line;
|
||||
|
||||
// Stroke
|
||||
if (s.width) {
|
||||
line = this.targetGroup('stroke',s.sublayer)
|
||||
.createPolyline(coords)
|
||||
.setStroke(s.strokeStyler());
|
||||
|
||||
this.recordSprite(line);
|
||||
maxwidth = Math.max(maxwidth, s.width);
|
||||
drawn = true;
|
||||
}
|
||||
|
||||
// Fill
|
||||
if (!isNaN(s.fill_color)) {
|
||||
line = this.targetGroup('fill',s.sublayer)
|
||||
.createPolyline(coords)
|
||||
.setFill(s.fillStyler());
|
||||
|
||||
this.recordSprite(line);
|
||||
drawn = true;
|
||||
}
|
||||
|
||||
// Casing
|
||||
if (s.casing_width) {
|
||||
line = this.targetGroup('casing')
|
||||
.createPolyline(coords)
|
||||
.setStroke(s.casingStyler());
|
||||
|
||||
this.recordSprite(line);
|
||||
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]
|
||||
}));
|
||||
|
||||
var entityMouseEvent = _.bind(this.entityMouseEvent, this);
|
||||
hit.source=this;
|
||||
hit.connect("onclick", entityMouseEvent);
|
||||
hit.connect("onmousedown", entityMouseEvent);
|
||||
hit.connect("onmouseup", entityMouseEvent);
|
||||
hit.connect("onmouseenter", entityMouseEvent);
|
||||
hit.connect("onmouseleave", entityMouseEvent);
|
||||
}
|
||||
// Draw nodes
|
||||
for (i=0; i<way.nodes.length; i++) {
|
||||
var node=way.nodes[i];
|
||||
var sc=[];
|
||||
if (tags[':shownodes']) { sc.push('selectedway'); }
|
||||
if (tags[':shownodeshover']) { sc.push('hoverway'); }
|
||||
if (node.entity.parentWays().length>1) { sc.push('junction'); }
|
||||
this.map.createUI(node,sc);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
entityMouseEvent:function(event) {
|
||||
this.inherited(arguments);
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
1
js/iD/renderer/renderer.js
Normal file
1
js/iD/renderer/renderer.js
Normal file
@@ -0,0 +1 @@
|
||||
iD.renderer = {};
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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) {
|
||||
// summary: A condition to evaluate.
|
||||
this.type =_type;
|
||||
this.params=Array.prototype.slice.call(arguments,1);
|
||||
},
|
||||
|
||||
test:function(tags) {
|
||||
// summary: Run the condition against the supplied tags.
|
||||
var p=this.params;
|
||||
switch (this.type) {
|
||||
case 'eq': return (tags[p[0]]==p[1]);
|
||||
case 'ne': return (tags[p[0]]!=p[1]);
|
||||
case 'regex': var r=new RegExp(p[1],"i");
|
||||
return (r.test(tags[p[0]]));
|
||||
case 'true': return (tags[p[0]]=='true' || tags[p[0]]=='yes' || tags[p[0]]=='1');
|
||||
case 'false': return (tags[p[0]]=='false' || tags[p[0]]=='no' || tags[p[0]]=='0');
|
||||
case 'set': return (tags[p[0]] !== undefined && tags[p[0]]!=='');
|
||||
case 'unset': return (tags[p[0]] === undefined || tags[p[0]]==='');
|
||||
case '<': return (Number(tags[p[0]])< Number(p[1]));
|
||||
case '<=': return (Number(tags[p[0]])<=Number(p[1]));
|
||||
case '>': return (Number(tags[p[0]])> Number(p[1]));
|
||||
case '>=': return (Number(tags[p[0]])>=Number(p[1]));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
return "["+this.type+": "+this.params+"]";
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
// iD/styleparser/Rule.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array'], function(declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Rule 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) {
|
||||
// summary: 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.
|
||||
this.subject=_subject;
|
||||
this.conditions=[];
|
||||
},
|
||||
|
||||
addCondition: function(_condition) {
|
||||
// summary: Add a condition to this rule.
|
||||
this.conditions.push(_condition);
|
||||
},
|
||||
|
||||
test: function(entity,tags,zoom) {
|
||||
// summary: Evaluate the Rule on the given entity, tags and zoom level.
|
||||
// returns: true if the Rule passes, false if the conditions aren't fulfilled.
|
||||
if ((this.subject !== '') && (entity.entityType !== 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
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
// 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() {
|
||||
// summary: A descendant list of MapCSS selectors (Rules).
|
||||
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';
|
||||
},
|
||||
|
||||
// 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) {
|
||||
// summary: Test a rule chain by running all the tests in reverse order.
|
||||
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.entity.parentObjects();
|
||||
for (var i = 0; i < o.length; i++) {
|
||||
var p=o[i];
|
||||
if (this.test(pos-1, p, p.tags, zoom)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
@@ -1,458 +0,0 @@
|
||||
// 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
|
||||
// doesn't do untagged nodes optimisation
|
||||
|
||||
declare("iD.styleparser.RuleSet", null, {
|
||||
|
||||
choosers: [], // list of StyleChoosers
|
||||
callback: null,
|
||||
|
||||
constructor: function() {
|
||||
// summary: An entire stylesheet in parsed form.
|
||||
this.choosers = [];
|
||||
},
|
||||
|
||||
registerCallback: function(callback) {
|
||||
// summary: Set a callback function to be called when the CSS is loaded and parsed.
|
||||
this.callback = callback;
|
||||
},
|
||||
|
||||
getStyles: function(entity, tags, zoom) {
|
||||
// summary: Find the styles for a given entity.
|
||||
var sl=new iD.styleparser.StyleList();
|
||||
for (var i in this.choosers) {
|
||||
this.choosers[i].updateStyles(entity, tags, sl, zoom);
|
||||
}
|
||||
return sl; // iD.styleparser.StyleList
|
||||
},
|
||||
|
||||
loadFromCSS:function(url) {
|
||||
// summary: Load a MapCSS file from a URL, then throw it at the parser when it's loaded.
|
||||
xhr.get({ url: url, load: lang.hitch(this, "parseCSS") });
|
||||
},
|
||||
|
||||
parseCSS:function(css) {
|
||||
// summary: Parse a CSS document into a set of StyleChoosers.
|
||||
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
|
||||
});
|
||||
@@ -1,240 +0,0 @@
|
||||
// iD/styleparser/Style.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/array'], function(declare,array){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Style base class
|
||||
// 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() {
|
||||
// summary: Base class for a set of painting attributes, into which the CSS declaration is parsed.
|
||||
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
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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() {
|
||||
// summary: A combination of the selectors (ruleChains) and declaration (styles).
|
||||
// For example, way[highway=footway] node[barrier=gate] { icon: gate.png; } is one StyleChooser.
|
||||
this.ruleChains=[new iD.styleparser.RuleChain()];
|
||||
this.styles=[];
|
||||
},
|
||||
|
||||
currentChain:function() {
|
||||
return this.ruleChains[this.ruleChains.length-1];
|
||||
},
|
||||
|
||||
newRuleChain:function() {
|
||||
// summary: 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
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
// iD/styleparser/StyleList.js
|
||||
|
||||
define(['dojo/_base/declare'], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// StyleList class
|
||||
|
||||
|
||||
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() {
|
||||
// summary: 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.
|
||||
this.shapeStyles={};
|
||||
this.textStyles={};
|
||||
this.pointStyles={};
|
||||
this.shieldStyles={};
|
||||
this.subparts=[];
|
||||
},
|
||||
|
||||
hasStyles:function() {
|
||||
// summary: Does this StyleList contain any styles?
|
||||
return (this.hasShapeStyles() || this.hasTextStyles() || this.hasPointStyles() || this.hasShieldStyles());
|
||||
},
|
||||
|
||||
hasFills:function() {
|
||||
// summary: Does this StyleList contain any styles with a fill?
|
||||
for (var s in this.shapeStyles) {
|
||||
if (!isNaN(this.shapeStyles(s).fill_color) || this.shapeStyles(s).fill_image) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
layerOverride:function() {
|
||||
// summary: If this StyleList manually forces an OSM layer, return it, otherwise null.
|
||||
for (var s in this.shapeStyles) {
|
||||
if (!isNaN(this.shapeStyles[s].layer)) return this.shapeStyles[s].layer;
|
||||
}
|
||||
return NaN;
|
||||
},
|
||||
|
||||
addSubpart:function(s) {
|
||||
// summary: Record that a subpart is used in this StyleList.
|
||||
if (this.subparts.indexOf(s)==-1) { this.subparts.push(s); }
|
||||
},
|
||||
|
||||
isValidAt:function(zoom) {
|
||||
// summary: Is this StyleList valid at a given zoom?
|
||||
return (this.validAt==-1 || this.validAt==zoom);
|
||||
},
|
||||
|
||||
toString:function() {
|
||||
// summary: Summarise StyleList as String - for debugging
|
||||
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
|
||||
});
|
||||
4
js/lib/d3.v2.min.js
vendored
Normal file
4
js/lib/d3.v2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user