|
|
|
|
@@ -1,11 +1,11 @@
|
|
|
|
|
// iD/renderer/Map.js
|
|
|
|
|
// at present this combines P2's Map and MapPaint functionality
|
|
|
|
|
|
|
|
|
|
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/event','dojo/_base/lang',
|
|
|
|
|
define(['dojo/_base/declare','dojo/_base/event','dojo/_base/lang',
|
|
|
|
|
'dojo/dom-geometry',
|
|
|
|
|
'dojox/gfx','dojox/gfx/matrix',
|
|
|
|
|
'iD/Connection','iD/Entity','iD/renderer/EntityUI','iD/renderer/WayUI','iD/renderer/NodeUI'],
|
|
|
|
|
function(declare, array, Event, lang, domGeom, Gfx, Matrix){
|
|
|
|
|
function(declare, Event, lang, domGeom, Gfx, Matrix){
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
|
// Connection base class
|
|
|
|
|
@@ -15,8 +15,8 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
MASTERSCALE: 5825.4222222222,
|
|
|
|
|
MINSCALE: 14,
|
|
|
|
|
MAXSCALE: 23,
|
|
|
|
|
scale: NaN,
|
|
|
|
|
scalefactor: NaN,
|
|
|
|
|
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
|
|
|
|
|
@@ -31,7 +31,7 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
wayuis: {}, // |
|
|
|
|
|
|
|
|
|
|
tilegroup: null, // group within container for adding bitmap tiles
|
|
|
|
|
tiles: [], // index of tile objects
|
|
|
|
|
tiles: {}, // index of tile objects
|
|
|
|
|
tilebaseURL: 'http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z', // Bing imagery URL
|
|
|
|
|
|
|
|
|
|
dragging: false, // current drag state
|
|
|
|
|
@@ -68,8 +68,8 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
this.mapheight = obj.height ? obj.height : 400;
|
|
|
|
|
|
|
|
|
|
// Initialise variables
|
|
|
|
|
this.nodeuis={},
|
|
|
|
|
this.wayuis={},
|
|
|
|
|
this.nodeuis = {},
|
|
|
|
|
this.wayuis = {},
|
|
|
|
|
this.div=document.getElementById(obj.div);
|
|
|
|
|
this.surface=Gfx.createSurface(obj.div, this.mapwidth, this.mapheight);
|
|
|
|
|
this.backdrop=this.surface.createRect({
|
|
|
|
|
@@ -81,7 +81,7 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
this.tilegroup = this.surface.createGroup();
|
|
|
|
|
this.container = this.surface.createGroup();
|
|
|
|
|
this.conn = obj.connection;
|
|
|
|
|
this.scale = obj.scale ? obj.scale : 17;
|
|
|
|
|
this.zoom = obj.zoom ? obj.zoom : 17;
|
|
|
|
|
this.baselon = obj.lon;
|
|
|
|
|
this.baselat = obj.lat;
|
|
|
|
|
this.baselatp = this.lat2latp(obj.lat);
|
|
|
|
|
@@ -246,21 +246,26 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (remove !== false) {
|
|
|
|
|
array.forEach(o.waysOutside, function(way) {
|
|
|
|
|
_.each(o.waysOutside, function(way) {
|
|
|
|
|
if (m.wayuis[way.id]) { // && !m.wayuis[way.id].purgable
|
|
|
|
|
if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); }
|
|
|
|
|
} else { m.deleteUI(way); }
|
|
|
|
|
if (redraw) {
|
|
|
|
|
m.wayuis[way.id].recalculate();
|
|
|
|
|
m.wayuis[way.id].redraw();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
m.deleteUI(way);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
array.forEach(o.poisInside, function(poi) {
|
|
|
|
|
_.each(o.poisInside, function(poi) {
|
|
|
|
|
if (!poi.loaded) return;
|
|
|
|
|
if (!m.nodeuis[poi.id]) { m.createUI(poi); }
|
|
|
|
|
else if (redraw) { m.nodeuis[poi.id].redraw(); }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (remove !== false) {
|
|
|
|
|
array.forEach(o.poisOutside, function(poi) {
|
|
|
|
|
_.each(o.poisOutside, function(poi) {
|
|
|
|
|
if (m.nodeuis[poi.id]) { // && !m.nodeuis[poi.id].purgable
|
|
|
|
|
if (redraw) { m.nodeuis[poi.id].redraw(); }
|
|
|
|
|
} else { m.deleteUI(poi); }
|
|
|
|
|
@@ -273,27 +278,31 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
|
|
|
|
|
zoomIn: function() {
|
|
|
|
|
// summary: Zoom in by one level (unless maximum reached).
|
|
|
|
|
if (this.scale !== this.MAXSCALE) { this.changeScale(this.scale+1); }
|
|
|
|
|
if (this.zoom !== this.MAXSCALE) {
|
|
|
|
|
this.changeScale(this.zoom + 1);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
zoomOut: function() {
|
|
|
|
|
// summary: Zoom out by one level (unless minimum reached).
|
|
|
|
|
if (this.scale !== this.MINSCALE) { this.changeScale(this.scale-1); }
|
|
|
|
|
if (this.zoom !== this.MINSCALE) {
|
|
|
|
|
this.changeScale(this.zoom - 1);
|
|
|
|
|
}
|
|
|
|
|
this.download();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
changeScale: function(scale) {
|
|
|
|
|
changeScale: function(zoom) {
|
|
|
|
|
// summary: Redraw the map at a new zoom level.
|
|
|
|
|
this.scale=scale;
|
|
|
|
|
this.zoom = zoom;
|
|
|
|
|
this._setScaleFactor();
|
|
|
|
|
this._blankTiles();
|
|
|
|
|
this.updateCoordsFromLatLon(this.centrelat,this.centrelon); // recentre
|
|
|
|
|
this.updateUIs(true,true);
|
|
|
|
|
this.updateCoordsFromLatLon(this.centrelat, this.centrelon); // recentre
|
|
|
|
|
this.updateUIs(true, true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_setScaleFactor: function() {
|
|
|
|
|
// summary: Calculate the scaling factor for this zoom level.
|
|
|
|
|
this.scalefactor=this.MASTERSCALE/Math.pow(2,13-this.scale);
|
|
|
|
|
this.zoomfactor = this.MASTERSCALE/Math.pow(2,13-this.zoom);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
|
@@ -323,56 +332,67 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
// 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 tile_l=this.lon2tile(this.extent.west);
|
|
|
|
|
var tile_r=this.lon2tile(this.extent.east);
|
|
|
|
|
var tile_t=this.lat2tile(this.extent.north);
|
|
|
|
|
var tile_b=this.lat2tile(this.extent.south);
|
|
|
|
|
var tile_l = this.lon2tile(this.extent.west);
|
|
|
|
|
var tile_r = this.lon2tile(this.extent.east);
|
|
|
|
|
var tile_t = this.lat2tile(this.extent.north);
|
|
|
|
|
var tile_b = this.lat2tile(this.extent.south);
|
|
|
|
|
|
|
|
|
|
for (var x=tile_l; x<=tile_r; x++) {
|
|
|
|
|
for (var y=tile_t; y<=tile_b; y++) {
|
|
|
|
|
if (!this._getTile(this.scale,x,y)) {
|
|
|
|
|
this._fetchTile(this.scale,x,y);
|
|
|
|
|
var tileKeys = _.keys(this.tiles);
|
|
|
|
|
var seen = [];
|
|
|
|
|
|
|
|
|
|
var coord = { z: this.zoom };
|
|
|
|
|
|
|
|
|
|
for (coord.x = tile_l; coord.x <= tile_r; coord.x++) {
|
|
|
|
|
for (coord.y = tile_t; coord.y <= tile_b; coord.y++) {
|
|
|
|
|
if (!this._getTile(coord)) {
|
|
|
|
|
this._fetchTile(coord);
|
|
|
|
|
}
|
|
|
|
|
seen.push(iD.Util.tileKey(coord));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_.each(_.without(tileKeys, seen), _.bind(function(key) {
|
|
|
|
|
delete this.tiles[key];
|
|
|
|
|
}, this));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_fetchTile: function(z,x,y) {
|
|
|
|
|
_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(x))),
|
|
|
|
|
y: Math.floor(this.lat2coord(this.tile2lat(y))),
|
|
|
|
|
width: 256, height: 256,
|
|
|
|
|
src: this._tileURL(z,x,y)
|
|
|
|
|
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)
|
|
|
|
|
});
|
|
|
|
|
this._assignTile(z,x,y,t);
|
|
|
|
|
this._assignTile(coord, t);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getTile: function(z,x,y) {
|
|
|
|
|
_getTile: function(coord) {
|
|
|
|
|
// summary: See if this tile is already loaded.
|
|
|
|
|
var k = z + ',' + x + ',' + y;
|
|
|
|
|
return this.tiles[k];
|
|
|
|
|
return this.tiles[iD.Util.tileKey(coord)];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_assignTile: function(z,x,y,t) {
|
|
|
|
|
_assignTile: function(coord, t) {
|
|
|
|
|
// summary: Store a reference to the tile so we know it's loaded.
|
|
|
|
|
var k = z + ',' + x + ',' + y;
|
|
|
|
|
if (!this.tiles[k]) {
|
|
|
|
|
this.tiles[z + ',' + x + ',' + y] = t;
|
|
|
|
|
}
|
|
|
|
|
this.tiles[iD.Util.tileKey(coord)] = t;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_tileURL: function(z,x,y) {
|
|
|
|
|
_tileURL: function(coord) {
|
|
|
|
|
// summary: Calculate the URL for a tile at the given co-ordinates.
|
|
|
|
|
var u='';
|
|
|
|
|
for (var zoom=z; zoom>0; zoom--) {
|
|
|
|
|
var byte=0;
|
|
|
|
|
var mask=1<<(zoom-1);
|
|
|
|
|
if ((x & mask) !== 0) byte++;
|
|
|
|
|
if ((y & mask) !== 0) byte += 2;
|
|
|
|
|
u=u+byte.toString();
|
|
|
|
|
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 = u + byte.toString();
|
|
|
|
|
}
|
|
|
|
|
return this.tilebaseURL.replace('$z',z).replace('$x',x).replace('$y',y).replace('$quadkey',u);
|
|
|
|
|
return this.tilebaseURL
|
|
|
|
|
.replace('$z', coord.z)
|
|
|
|
|
.replace('$x', coord.x)
|
|
|
|
|
.replace('$y', coord.y)
|
|
|
|
|
.replace('$quadkey', u);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_blankTiles: function() {
|
|
|
|
|
@@ -387,14 +407,15 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
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;
|
|
|
|
|
var srcElement = (e.gfxTarget === this.backdrop) ?
|
|
|
|
|
e.gfxTarget : e.gfxTarget.parent;
|
|
|
|
|
Event.stop(e);
|
|
|
|
|
this.dragging=true;
|
|
|
|
|
this.dragged=false;
|
|
|
|
|
this.dragx=this.dragy=NaN;
|
|
|
|
|
this.startdragx=e.clientX;
|
|
|
|
|
this.startdragy=e.clientY;
|
|
|
|
|
this.dragconnect=srcElement.connect("onmouseup", lang.hitch(this,"endDrag"));
|
|
|
|
|
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) {
|
|
|
|
|
@@ -405,7 +426,10 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
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; }
|
|
|
|
|
if (Math.abs(e.clientX - this.startdragx) < 3 &&
|
|
|
|
|
Math.abs(e.clientY - this.startdragy) < 3) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.download();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
@@ -421,8 +445,8 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
this.updateOrigin();
|
|
|
|
|
this.dragged=true;
|
|
|
|
|
}
|
|
|
|
|
this.dragx=x;
|
|
|
|
|
this.dragy=y;
|
|
|
|
|
this.dragx = x;
|
|
|
|
|
this.dragy = y;
|
|
|
|
|
} else {
|
|
|
|
|
this.controller.entityMouseEvent(e,null);
|
|
|
|
|
}
|
|
|
|
|
@@ -430,8 +454,8 @@ declare("iD.renderer.Map", 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)]);
|
|
|
|
|
this.container.setTransform([Matrix.translate(this.containerx, this.containery)]);
|
|
|
|
|
this.tilegroup.setTransform([Matrix.translate(this.containerx, this.containery)]);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_mouseEvent: function(e) {
|
|
|
|
|
@@ -455,10 +479,12 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
|
|
|
|
|
_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.containerx = x;
|
|
|
|
|
this.containery = y;
|
|
|
|
|
this.updateOrigin();
|
|
|
|
|
this.centrelon = this.coord2lon(-x + this.mapwidth/2);
|
|
|
|
|
this.centrelat = this.coord2lat(-y + this.mapheight/2);
|
|
|
|
|
|
|
|
|
|
this.extent = {
|
|
|
|
|
north: this.coord2lat(-y),
|
|
|
|
|
south: this.coord2lat(-y + this.mapheight),
|
|
|
|
|
@@ -478,23 +504,32 @@ declare("iD.renderer.Map", null, {
|
|
|
|
|
// -----------------------
|
|
|
|
|
// Co-ordinate conversions
|
|
|
|
|
|
|
|
|
|
latp2coord:function(a) { return -(a-this.baselatp)*this.scalefactor; },
|
|
|
|
|
coord2latp:function(a) { return a/-this.scalefactor+this.baselatp; },
|
|
|
|
|
lon2coord:function(a) { return (a-this.baselon)*this.scalefactor; },
|
|
|
|
|
coord2lon:function(a) { return a/this.scalefactor+this.baselon; },
|
|
|
|
|
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) + domGeom.getMarginBox(this.div).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.scalefactor; },
|
|
|
|
|
coord2lat:function(a) { return this.latp2lat(a/-this.scalefactor+this.baselatp); },
|
|
|
|
|
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) + domGeom.getMarginBox(this.div).t + this.containery; },
|
|
|
|
|
|
|
|
|
|
lon2tile:function(a) { return (Math.floor((a+180)/360*Math.pow(2,this.scale))); },
|
|
|
|
|
lat2tile:function(a) { return (Math.floor((1-Math.log(Math.tan(a*Math.PI/180) + 1/Math.cos(a*Math.PI/180))/Math.PI)/2 *Math.pow(2,this.scale))); },
|
|
|
|
|
tile2lon:function(a) { return (a/Math.pow(2,this.scale)*360-180); },
|
|
|
|
|
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.scale);
|
|
|
|
|
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))));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|