Massive rework

This commit is contained in:
Tom MacWright
2012-10-23 22:57:27 -04:00
parent 6d07b7f45d
commit 0264d66fe7
16 changed files with 286 additions and 535 deletions
+12
View File
@@ -38,6 +38,18 @@ table th {
text-align:left;
}
path.casing {
fill: transparent;
stroke: #ace;
stroke-width: 6;
}
path.stroke {
fill: transparent;
stroke: #5F8594;
stroke-width: 4;
}
.help-pane {
position:absolute;
left:0;
+17 -13
View File
@@ -4,7 +4,6 @@
<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">
</head>
<body class="claro">
@@ -15,9 +14,18 @@
<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/controller/controller.js"></script>
<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>
<script type="text/javascript" src="js/iD/styleparser/styleparser.js"></script>
<script type="text/javascript" src="js/iD/styleparser/Condition.js"></script>
<script type="text/javascript" src="js/iD/styleparser/Rule.js"></script>
<script type="text/javascript" src="js/iD/styleparser/RuleChain.js"></script>
<script type="text/javascript" src="js/iD/styleparser/Style.js"></script>
<script type="text/javascript" src="js/iD/styleparser/StyleChooser.js"></script>
<script type="text/javascript" src="js/iD/styleparser/StyleList.js"></script>
@@ -86,28 +94,27 @@
<!-- Map 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">&copy; 2012</a> Bing, GeoEye, Getmapping, Intermap, Microsoft.</p>
<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">&copy; 2012</a> Bing, GeoEye, Getmapping, Intermap, Microsoft.</p>
</div>
<script>
var ruleset = new iD.styleparser.RuleSet();
var connection = new iD.Connection("http://www.overpass-api.de/api/xapi?");
// Load styles
ruleset.registerCallback(styleLoaded);
ruleset.loadFromCSS("potlatch.css", styleLoaded);
// Initialise map
var map = new iD.renderer.Map({
lat: 51.87,
lon: -1.49,
zoom: 17,
selector: "#map",
connection: connection,
width: $('#map').width(),
height: $('#map').height()
});
map.setZoom(14);
map.setCentre({ lat: 38.8, lon: -77 });
map.ruleset = ruleset;
// Initialise controller
@@ -116,16 +123,13 @@
// ----------------------------------------------------
// Data is loaded and app ready to go
function styleLoaded() {
// Initialise drag-and-drop icons
new iD.ui.DragAndDrop("map", map, "dndgrid");
ruleset.loadFromCSS("potlatch.css", function styleLoaded() {
// Set initial controllerState
controller.setState(new iD.controller.edit.NoSelection());
// Load data
map.download();
}
});
// ----------------------------------------------------
// Mode button handlers
+2 -1
View File
@@ -3,7 +3,8 @@
if (typeof iD === 'undefined') iD = {};
iD.Controller = function(map) {
var controller = {};
var controller = {},
state = null;
controller.editorCache = {};
controller.undoStack = new iD.UndoStack();
+1
View File
@@ -0,0 +1 @@
iD.controller = {};
+3 -12
View File
@@ -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
});
};
+4 -16
View File
@@ -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
});
};
+35 -42
View File
@@ -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
});
};
+3 -10
View File
@@ -1,11 +1,8 @@
// 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,
@@ -57,8 +54,4 @@ declare("iD.controller.edit.SelectedWay", [iD.controller.edit.EditBaseState], {
return this;
}
});
// ----------------------------------------------------------------------
// End of module
});
};
+3 -11
View File
@@ -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
View File
@@ -0,0 +1 @@
iD.controller.edit = {};
+91 -236
View File
@@ -8,29 +8,27 @@ iD.renderer.Map = 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.
this.mapwidth = obj.width ? obj.width : 800;
this.mapheight = obj.height ? obj.height : 400;
this.width = obj.width ? obj.width : 800;
this.height = obj.height ? obj.height : 400;
// Initialise variables
this.uis = {};
this.projection = d3.geo.mercator()
.scale(512).translate([512, 512]);
this.zoombehavior = d3.behavior.zoom()
.translate(this.projection.translate())
.scale(this.projection.scale())
.on("zoom", _.bind(this.redraw, this));
this.surface = d3.selectAll(obj.selector)
.append('svg')
.attr('width', this.mapwidth)
.attr('height', this.mapwidth);
.attr({ width: this.width, height: this.width })
.call(this.zoombehavior);
this.tilegroup = this.surface.append('g');
this.container = this.surface.append('g');
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();
// Cache the margin box, since this is expensive.
// this.marginBox = domGeom.getMarginBox(this.div);
// Initialise layers
this.layers={};
@@ -50,22 +48,12 @@ iD.renderer.Map = function(obj) {
this.elastic = this.container.append('g');
// Make draggable
this.tilegroup.on('onmousedown', _.bind(this.startDrag, this));
this.surface.on('onclick', _.bind(this.clickSurface, this));
this.surface.on('onmousemove', _.bind(this.processMove, this));
this.surface.on('onmousedown', _.bind(this._mouseEvent, this));
this.surface.on('onmouseup', _.bind(this._mouseEvent, this));
};
iD.renderer.Map.prototype = {
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
this.redraw();
};
iD.renderer.Map.prototype = {
div: '', // <div> of this map
surface: null, // <div>.surface containing the rendering
@@ -81,20 +69,14 @@ iD.renderer.Map.prototype = {
dragging: false, // current drag state
dragged: false, // was most recent click a drag?
dragx: NaN, // click co-ordinates at previously recorded drag event
dragy: NaN, // |
startdragx: NaN, // click co-ordinates at start of drag
startdragy: NaN, // |
dragtime: NaN, // timestamp of mouseup (compared to stop resulting click from firing)
dragconnect: null, // event listener for endDrag
containerx: 0, // screen co-ordinates of container
containery: 0, // |
centrelat: NaN, // lat/long and bounding box of map
centrelon: NaN, // |
extent: {}, // |
mapheight: NaN, // size of map object in pixels
mapwidth: NaN, // |
height: NaN, // size of map object in pixels
width: NaN, // |
layers: null, // array-like object of Groups, one for each OSM layer
minlayer: -5, // minimum OSM layer supported
@@ -134,7 +116,7 @@ iD.renderer.Map.prototype = {
// ----------------------------
// Sprite and EntityUI handling
sublayer:function(layer,groupType,sublayer) {
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'
@@ -205,18 +187,18 @@ iD.renderer.Map.prototype = {
// remove: Boolean Should we delete any UIs that are no longer in the bbox?
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) {
this.deleteUI(k);
}, this));
.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) {
this.deleteUI(k);
}, this));
},
// -------------
@@ -235,16 +217,11 @@ iD.renderer.Map.prototype = {
},
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
});
this.projection.scale(256 * Math.pow(2, zoom - 1));
this.zoombehavior.scale(this.projection.scale());
this.updateUIs(true, true);
this.redraw();
return this;
},
@@ -272,149 +249,60 @@ iD.renderer.Map.prototype = {
width: 1
});
},
redraw: function() {
var projection = this.projection,
width = this.width,
height = this.height;
// -------------
// 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 };
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));
}
if (d3.event) {
projection
.translate(d3.event.translate)
.scale(d3.event.scale);
}
_.each(_.without(tileKeys, seen), _.bind(function(key) {
delete this.tiles[key];
}, this));
},
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);
_fetchTile: function(coord) {
// summary: Load a tile image at the given tile co-ordinates.
var t = this.tilegroup.append('image')
.attr({
width: 256,
height: 256,
x: Math.floor(this.lon2coord(this.tile2lon(coord.x))),
y: Math.floor(this.lat2coord(this.tile2lat(coord.y))),
'xlink:href': this._tileURL(coord)
// 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)));
cols.forEach(function(x) {
rows.forEach(function(y) { coords.push([Math.floor(z), x, y]); });
});
var tmpl = this.tilebaseURL;
function tileUrl(coord) {
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);
}
var tiles = this.tilegroup.selectAll('image.tile')
.data(coords, function(d) { return d.join(','); });
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]] + ')';
});
this._assignTile(coord, t);
},
_getTile: function(coord) {
// summary: See if this tile is already loaded.
return this.tiles[iD.Util.tileKey(coord)];
},
_assignTile: function(coord, t) {
// summary: Store a reference to the tile so we know it's loaded.
this.tiles[iD.Util.tileKey(coord)] = t;
},
_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);
},
_blankTiles: function() {
// summary: Unload all tiles and remove from the display.
this.tilegroup.clear();
this.tiles = {};
},
// -------------------------------------------
// Co-ordinate management, dragging and redraw
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));
},
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();
},
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);
}
},
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)]);
},
_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) {
@@ -424,11 +312,14 @@ iD.renderer.Map.prototype = {
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;
var t = this.projection.translate(),
ll = this.projection([loc.lon, loc.lat]);
this.projection.translate([
t[0] - ll[0] + this.width / 2,
t[1] - ll[1] + this.height / 2]);
this.zoombehavior.translate(this.projection.translate());
this.redraw();
return this;
},
setCenter: function(loc) { this.setCentre(loc); },
@@ -437,7 +328,6 @@ iD.renderer.Map.prototype = {
// 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);
@@ -447,8 +337,6 @@ iD.renderer.Map.prototype = {
west: this.coord2lon(-x),
east: this.coord2lon(-x + this.mapwidth)
};
this.loadTiles();
},
clickSurface:function(e) {
@@ -457,40 +345,7 @@ iD.renderer.Map.prototype = {
this.controller.entityMouseEvent(e,null);
},
// -----------------------
// Co-ordinate conversions
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; },
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; },
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; }
};
+21 -85
View File
@@ -1,94 +1,30 @@
iD.renderer.NodeUI = function() {};
iD.renderer.NodeUI = function(node, map) {
this.node = node;
this.map = map;
this.draw();
};
iD.renderer.NodeUI.prototype = {
getEnhancedTags:function() {
var tags=this.inherited(arguments);
if (!this.entity.entity.hasParentWays()) { tags[':poi']='yes'; }
getEnhancedTags: function() {
var tags = this.node.tags;
if (!this.node.entity.hasParentWays()) { tags[':poi']='yes'; }
// add junction and dupe
return tags;
},
redraw:function() {
draw: 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 x = Math.floor(this.map.lon2coord(this.node.lon));
var y = Math.floor(this.map.latp2coord(this.node.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));
}
}
var im = this.map.layers[0].hit.append("image")
.attr('class', 'poi')
.attr('x', x)
.attr('y', y)
.attr('width', 16)
.attr('height', 16)
.attr("xlink:href", function() {
return tags.amenity ? 'icons/' + tags.amenity + '.png' : '';
});
}
};
+19 -92
View File
@@ -8,30 +8,28 @@
// ----------------------------------------------------------------------
// WayUI class
iD.renderer.WayUI = function() {};
iD.renderer.WayUI = function(entity, map) {
this.entity = entity;
this.map = map;
this.draw();
};
iD.renderer.WayUI.prototype = {
getEnhancedTags: function() {
var tags = this.inherited(arguments);
var tags = this.entity.tags;
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() {
draw: 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) {
@@ -41,91 +39,20 @@ iD.renderer.WayUI.prototype = {
};
}, 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;
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
// Stroke
if (s.width) {
line = this.targetGroup('stroke',s.sublayer)
.createPolyline(coords)
.setStroke(s.strokeStyler());
this.map.layers[0].casing.append("path")
.data([coords])
.attr('class', 'casing')
.attr("d", line);
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);
}
this.map.layers[0].stroke.append("path")
.data([coords])
.attr('class', 'stroke')
.attr("d", line);
return this;
},
+1 -5
View File
@@ -67,8 +67,4 @@ iD.styleparser.RuleChain.prototype = {
}
return false;
}
});
// ----------------------------------------------------------------------
// End of module
});
};
+4 -9
View File
@@ -7,12 +7,6 @@ iD.styleparser.RuleSet = function() {};
iD.styleparser.RuleSet.prototype = {
choosers: [], // list of StyleChoosers
callback: null,
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.
@@ -23,15 +17,16 @@ iD.styleparser.RuleSet.prototype = {
return sl; // iD.styleparser.StyleList
},
loadFromCSS:function(url) {
loadFromCSS: function(url, callback) {
// summary: Load a MapCSS file from a URL, then throw it at the parser when it's loaded.
this.callback = callback;
$.ajax({
url: url,
load: _.bind(this.parseCSS, this)
success: _.bind(this.parseCSS, this)
});
},
parseCSS:function(css) {
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
+69 -3
View File
@@ -7,11 +7,9 @@ iD.styleparser.Style.prototype = {
interactive: true,
properties: [],
styleType: 'Style',
evals: null,
evals: {},
constructor: function() {
// summary: Base class for a set of painting attributes, into which the CSS declaration is parsed.
this.evals = {};
},
drawn: function() {
@@ -77,6 +75,7 @@ iD.styleparser.InstructionStyle = function() {};
iD.styleparser.InstructionStyle.prototype = {
set_tags: null,
breaker: false,
evals: {},
styleType: 'InstructionStyle',
addSetTag: function(k,v) {
this.edited=true;
@@ -93,12 +92,34 @@ iD.styleparser.PointStyle.prototype = {
properties: ['icon_image','icon_width','icon_height','rotation'],
icon_image: null,
icon_width: 0,
evals: {},
icon_height: NaN,
rotation: NaN,
styleType: 'PointStyle',
drawn:function() {
return (this.icon_image !== null);
},
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;
},
has: function(k) {
return this.properties.indexOf(k)>-1;
},
maxwidth:function() {
return this.evals.icon_width ? 0 : this.icon_width;
}
@@ -118,9 +139,31 @@ iD.styleparser.ShapeStyle.prototype = {
fill_image:null, fill_color:NaN, fill_opacity:NaN,
casing_width:NaN, casing_color:NaN, casing_opacity:NaN, casing_dashes:[],
evals: {},
layer:NaN, // optional layer override (usually set by OSM tag)
styleType: 'ShapeStyle',
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;
},
has: function(k) {
return this.properties.indexOf(k)>-1;
},
drawn:function() {
return (this.fill_image || !isNaN(this.fill_color) || this.width || this.casing_width);
},
@@ -184,6 +227,7 @@ iD.styleparser.TextStyle.prototype = {
font_italic: false,
font_underline: false,
font_caps: false,
evals: {},
font_size: NaN,
text_color: NaN,
text_offset: NaN,
@@ -195,6 +239,23 @@ iD.styleparser.TextStyle.prototype = {
letter_spacing: 0,
styleType: 'TextStyle',
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;
},
drawn: function() {
return (this.text !== null);
},
@@ -213,6 +274,10 @@ iD.styleparser.TextStyle.prototype = {
text: _text
};
},
has: function(k) {
return this.properties.indexOf(k)>-1;
},
fillStyler:function() {
// not implemented yet
return this.dojoColor(0,1);
@@ -230,6 +295,7 @@ iD.styleparser.ShieldStyle.prototype = {
shield_image: null,
shield_width: NaN,
shield_height: NaN,
evals: {},
styleType: 'ShieldStyle',
drawn:function() {
return (shield_image !== null);