mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-29 15:16:07 +02:00
Merge pull request #2720 from openstreetmap/mapillary-streetsigns2
Mapillary street signs
This commit is contained in:
@@ -137,7 +137,7 @@ iD can use external presets exclusively or along with the default OpenStreetMap
|
||||
|
||||
var iD = iD()
|
||||
.presets(customPresets)
|
||||
.taginfo(iD.taginfo())
|
||||
.taginfo(iD.services.taginfo())
|
||||
.imagery(iD.data.imagery);
|
||||
|
||||
```
|
||||
@@ -152,7 +152,7 @@ Just like Presets, Imagery can be configured using the `iD().imagery` accessor.
|
||||
|
||||
var iD = iD()
|
||||
.presets(customPresets)
|
||||
.taginfo(iD.taginfo())
|
||||
.taginfo(iD.services.taginfo())
|
||||
.imagery(customImagery);
|
||||
|
||||
```
|
||||
@@ -168,7 +168,7 @@ The Imagery object should follow the structure defined by [editor-imagery-index]
|
||||
|
||||
var iD = iD()
|
||||
.presets(customPresets)
|
||||
.taginfo(iD.taginfo().endpoint('url'))
|
||||
.taginfo(iD.services.taginfo().endpoint('url'))
|
||||
.imagery(customImagery);
|
||||
|
||||
```
|
||||
|
||||
@@ -63,6 +63,7 @@ dist/iD.js: \
|
||||
js/lib/marked.js \
|
||||
js/id/start.js \
|
||||
js/id/id.js \
|
||||
js/id/services.js \
|
||||
js/id/services/*.js \
|
||||
js/id/util.js \
|
||||
js/id/util/*.js \
|
||||
|
||||
+6
-2
@@ -295,6 +295,8 @@ ul li { list-style: none;}
|
||||
|
||||
.fl { float: left;}
|
||||
.fr { float: right;}
|
||||
.al { left: 0; }
|
||||
.ar { right: 0; }
|
||||
|
||||
div.hide,
|
||||
form.hide,
|
||||
@@ -655,7 +657,6 @@ button.save.has-count .count::before {
|
||||
|
||||
.mapillary-image {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 30px;
|
||||
width: 330px;
|
||||
height: 250px;
|
||||
@@ -669,7 +670,6 @@ button.save.has-count .count::before {
|
||||
height: auto;
|
||||
background-color: rgba(0,0,0,.5);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
@@ -1797,6 +1797,10 @@ div.full-screen > button:hover {
|
||||
color: #7092FF;
|
||||
}
|
||||
|
||||
.layer-list:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layer-list > li:first-child {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
+50
-9
@@ -1514,43 +1514,84 @@ text.gpx {
|
||||
fill: #FF26D4;
|
||||
}
|
||||
|
||||
/* Mapillary Layer */
|
||||
/* Mapillary Image Layer */
|
||||
|
||||
.layer-mapillary {
|
||||
.layer-mapillary-images {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layer-mapillary g {
|
||||
.layer-mapillary-images .viewfield-group {
|
||||
pointer-events: visible;
|
||||
cursor: pointer; /* Opera */
|
||||
cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
|
||||
}
|
||||
|
||||
.layer-mapillary g * {
|
||||
.layer-mapillary-images .viewfield-group * {
|
||||
stroke-width: 1;
|
||||
stroke: #444;
|
||||
fill: #ffc600;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.layer-mapillary g:hover * {
|
||||
.layer-mapillary-images .viewfield-group:hover * {
|
||||
stroke-width: 1;
|
||||
stroke: #333;
|
||||
fill: #ff9900;
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.layer-mapillary g.selected * {
|
||||
.layer-mapillary-images .viewfield-group.selected * {
|
||||
stroke-width: 2;
|
||||
stroke: #222;
|
||||
fill: #ff5800;
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.layer-mapillary g:hover path.viewfield,
|
||||
.layer-mapillary g.selected path.viewfield,
|
||||
.layer-mapillary g path.viewfield {
|
||||
.layer-mapillary-images .viewfield-group:hover path.viewfield,
|
||||
.layer-mapillary-images .viewfield-group.selected path.viewfield,
|
||||
.layer-mapillary-images .viewfield-group path.viewfield {
|
||||
stroke-width: 0;
|
||||
fill-opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Mapillary Sign Layer */
|
||||
|
||||
.layer-mapillary-signs {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layer-mapillary-signs .icon-sign body {
|
||||
min-width: 20px;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 2px solid transparent;
|
||||
pointer-events: visible;
|
||||
cursor: pointer; /* Opera */
|
||||
cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
|
||||
z-index: 70;
|
||||
}
|
||||
|
||||
.layer-mapillary-signs .icon-sign:hover body {
|
||||
border: 2px solid rgba(255,198,0,0.8);
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.layer-mapillary-signs .icon-sign.selected body {
|
||||
border: 2px solid rgba(255,0,0,0.8);
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.layer-mapillary-signs .icon-sign .t {
|
||||
font-size: 28px;
|
||||
z-index: 70;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.layer-mapillary-signs .icon-sign:hover .t,
|
||||
.layer-mapillary-signs .icon-sign.selected .t {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
/* Modes */
|
||||
|
||||
.mode-draw-line .vertex.active,
|
||||
|
||||
+5
-1
@@ -415,9 +415,13 @@ en:
|
||||
drag_drop: "Drag and drop a .gpx file on the page, or click the button to the right to browse"
|
||||
zoom: "Zoom to GPX track"
|
||||
browse: "Browse for a .gpx file"
|
||||
mapillary:
|
||||
mapillary_images:
|
||||
tooltip: "Street-level photos from Mapillary"
|
||||
title: "Photo Overlay (Mapillary)"
|
||||
mapillary_signs:
|
||||
tooltip: "Traffic signs from Mapillary"
|
||||
title: "Traffic Sign Overlay (Mapillary)"
|
||||
mapillary:
|
||||
view_on_mapillary: "View this image on Mapillary"
|
||||
help:
|
||||
title: "Help"
|
||||
|
||||
Vendored
+8
-2
@@ -501,9 +501,15 @@
|
||||
"zoom": "Zoom to GPX track",
|
||||
"browse": "Browse for a .gpx file"
|
||||
},
|
||||
"mapillary": {
|
||||
"mapillary_images": {
|
||||
"tooltip": "Street-level photos from Mapillary",
|
||||
"title": "Photo Overlay (Mapillary)",
|
||||
"title": "Photo Overlay (Mapillary)"
|
||||
},
|
||||
"mapillary_signs": {
|
||||
"tooltip": "Traffic signs from Mapillary",
|
||||
"title": "Traffic Sign Overlay (Mapillary)"
|
||||
},
|
||||
"mapillary": {
|
||||
"view_on_mapillary": "View this image on Mapillary"
|
||||
},
|
||||
"help": {
|
||||
|
||||
+1
Submodule dist/traffico added at 2ff4ac82f1
+7
-3
@@ -6,6 +6,7 @@
|
||||
<link rel='stylesheet' href='css/reset.css'>
|
||||
<link rel='stylesheet' href='css/map.css'>
|
||||
<link rel='stylesheet' href='css/app.css'>
|
||||
<link rel='stylesheet' href='dist/traffico/stylesheets/traffico.css'>
|
||||
|
||||
<!-- mobile devices -->
|
||||
<meta name='viewport' content='initial-scale=1.0 maximum-scale=1.0'>
|
||||
@@ -39,7 +40,9 @@
|
||||
<script src='js/id/util/session_mutex.js'></script>
|
||||
<script src='js/id/util/suggest_names.js'></script>
|
||||
|
||||
<script src='js/id/services/countrycode.js'></script>
|
||||
<script src='js/id/services.js'></script>
|
||||
<script src='js/id/services/mapillary.js'></script>
|
||||
<script src='js/id/services/nominatim.js'></script>
|
||||
<script src='js/id/services/taginfo.js'></script>
|
||||
<script src='js/id/services/wikipedia.js'></script>
|
||||
|
||||
@@ -57,7 +60,8 @@
|
||||
<script src='js/id/renderer/gpx_layer.js'></script>
|
||||
<script src='js/id/renderer/tile_layer.js'></script>
|
||||
<script src='js/id/renderer/map.js'></script>
|
||||
<script src='js/id/renderer/mapillary_layer.js'></script>
|
||||
<script src='js/id/renderer/mapillary_image_layer.js'></script>
|
||||
<script src='js/id/renderer/mapillary_sign_layer.js'></script>
|
||||
|
||||
<script src="js/id/svg.js"></script>
|
||||
<script src="js/id/svg/areas.js"></script>
|
||||
@@ -249,7 +253,7 @@
|
||||
id = iD()
|
||||
.presets(iD.data.presets)
|
||||
.imagery(iD.data.imagery)
|
||||
.taginfo(iD.taginfo())
|
||||
.taginfo(iD.services.taginfo())
|
||||
.assetPath('dist/');
|
||||
|
||||
d3.select('#id-container')
|
||||
|
||||
+2
-11
@@ -2,17 +2,8 @@ iD.Tree = function(head) {
|
||||
var rtree = rbush(),
|
||||
rectangles = {};
|
||||
|
||||
function extentRectangle(extent) {
|
||||
return [
|
||||
extent[0][0],
|
||||
extent[0][1],
|
||||
extent[1][0],
|
||||
extent[1][1]
|
||||
];
|
||||
}
|
||||
|
||||
function entityRectangle(entity) {
|
||||
var rect = extentRectangle(entity.extent(head));
|
||||
var rect = entity.extent(head).rectangle();
|
||||
rect.id = entity.id;
|
||||
rectangles[entity.id] = rect;
|
||||
return rect;
|
||||
@@ -90,7 +81,7 @@ iD.Tree = function(head) {
|
||||
rtree.load(_.map(insertions, entityRectangle));
|
||||
}
|
||||
|
||||
return rtree.search(extentRectangle(extent)).map(function(rect) {
|
||||
return rtree.search(extent.rectangle()).map(function(rect) {
|
||||
return head.entity(rect.id);
|
||||
});
|
||||
};
|
||||
|
||||
+5
-1
@@ -45,6 +45,10 @@ _.extend(iD.geo.Extent.prototype, {
|
||||
(this[0][1] + this[1][1]) / 2];
|
||||
},
|
||||
|
||||
rectangle: function() {
|
||||
return [this[0][0], this[0][1], this[1][0], this[1][1]];
|
||||
},
|
||||
|
||||
polygon: function() {
|
||||
return [
|
||||
[this[0][0], this[0][1]],
|
||||
@@ -100,7 +104,7 @@ _.extend(iD.geo.Extent.prototype, {
|
||||
},
|
||||
|
||||
toParam: function() {
|
||||
return [this[0][0], this[0][1], this[1][0], this[1][1]].join(',');
|
||||
return this.rectangle().join(',');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
+105
-81
@@ -2,11 +2,12 @@ window.iD = function () {
|
||||
window.locale.en = iD.data.en;
|
||||
window.locale.current('en');
|
||||
|
||||
var context = {},
|
||||
storage;
|
||||
var dispatch = d3.dispatch('enter', 'exit'),
|
||||
context = {};
|
||||
|
||||
// https://github.com/openstreetmap/iD/issues/772
|
||||
// http://mathiasbynens.be/notes/localstorage-pattern#comment-9
|
||||
var storage;
|
||||
try { storage = localStorage; } catch (e) {} // eslint-disable-line no-empty
|
||||
storage = storage || (function() {
|
||||
var s = {};
|
||||
@@ -30,34 +31,7 @@ window.iD = function () {
|
||||
}
|
||||
};
|
||||
|
||||
/* Accessor for setting minimum zoom for editing features. */
|
||||
|
||||
var minEditableZoom = 16;
|
||||
context.minEditableZoom = function(_) {
|
||||
if (!arguments.length) return minEditableZoom;
|
||||
minEditableZoom = _;
|
||||
connection.tileZoom(_);
|
||||
return context;
|
||||
};
|
||||
|
||||
var history = iD.History(context),
|
||||
dispatch = d3.dispatch('enter', 'exit'),
|
||||
mode,
|
||||
container,
|
||||
ui = iD.ui(context),
|
||||
connection = iD.Connection(),
|
||||
locale = iD.detect().locale,
|
||||
localePath;
|
||||
|
||||
if (locale && iD.data.locales.indexOf(locale) === -1) {
|
||||
locale = locale.split('-')[0];
|
||||
}
|
||||
|
||||
context.preauth = function(options) {
|
||||
connection.switch(options);
|
||||
return context;
|
||||
};
|
||||
|
||||
var locale, localePath;
|
||||
context.locale = function(loc, path) {
|
||||
locale = loc;
|
||||
localePath = path;
|
||||
@@ -83,16 +57,24 @@ window.iD = function () {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Straight accessors. Avoid using these if you can. */
|
||||
var ui, connection, history;
|
||||
context.ui = function() { return ui; };
|
||||
context.connection = function() { return connection; };
|
||||
context.history = function() { return history; };
|
||||
|
||||
|
||||
/* Connection */
|
||||
function entitiesLoaded(err, result) {
|
||||
if (!err) history.merge(result.data, result.extent);
|
||||
}
|
||||
|
||||
context.preauth = function(options) {
|
||||
connection.switch(options);
|
||||
return context;
|
||||
};
|
||||
|
||||
context.loadTiles = function(projection, dimensions, callback) {
|
||||
function done(err, result) {
|
||||
entitiesLoaded(err, result);
|
||||
@@ -133,13 +115,17 @@ window.iD = function () {
|
||||
});
|
||||
};
|
||||
|
||||
var minEditableZoom = 16;
|
||||
context.minEditableZoom = function(_) {
|
||||
if (!arguments.length) return minEditableZoom;
|
||||
minEditableZoom = _;
|
||||
connection.tileZoom(_);
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* History */
|
||||
context.graph = history.graph;
|
||||
context.changes = history.changes;
|
||||
context.intersects = history.intersects;
|
||||
|
||||
var inIntro = false;
|
||||
|
||||
context.inIntro = function(_) {
|
||||
if (!arguments.length) return inIntro;
|
||||
inIntro = _;
|
||||
@@ -157,45 +143,34 @@ window.iD = function () {
|
||||
connection.flush();
|
||||
features.reset();
|
||||
history.reset();
|
||||
_.each(iD.services, function(service) {
|
||||
var reset = service().reset;
|
||||
if (reset) reset(context);
|
||||
});
|
||||
return context;
|
||||
};
|
||||
|
||||
// Debounce save, since it's a synchronous localStorage write,
|
||||
// and history changes can happen frequently (e.g. when dragging).
|
||||
context.debouncedSave = _.debounce(context.save, 350);
|
||||
function withDebouncedSave(fn) {
|
||||
return function() {
|
||||
var result = fn.apply(history, arguments);
|
||||
context.debouncedSave();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
context.perform = withDebouncedSave(history.perform);
|
||||
context.replace = withDebouncedSave(history.replace);
|
||||
context.pop = withDebouncedSave(history.pop);
|
||||
context.overwrite = withDebouncedSave(history.overwrite);
|
||||
context.undo = withDebouncedSave(history.undo);
|
||||
context.redo = withDebouncedSave(history.redo);
|
||||
|
||||
/* Graph */
|
||||
context.hasEntity = function(id) {
|
||||
return history.graph().hasEntity(id);
|
||||
};
|
||||
|
||||
context.entity = function(id) {
|
||||
return history.graph().entity(id);
|
||||
};
|
||||
|
||||
context.childNodes = function(way) {
|
||||
return history.graph().childNodes(way);
|
||||
};
|
||||
|
||||
context.geometry = function(id) {
|
||||
return context.entity(id).geometry(history.graph());
|
||||
};
|
||||
|
||||
|
||||
/* Modes */
|
||||
var mode;
|
||||
context.mode = function() {
|
||||
return mode;
|
||||
};
|
||||
context.enter = function(newMode) {
|
||||
if (mode) {
|
||||
mode.exit();
|
||||
@@ -207,10 +182,6 @@ window.iD = function () {
|
||||
dispatch.enter(mode);
|
||||
};
|
||||
|
||||
context.mode = function() {
|
||||
return mode;
|
||||
};
|
||||
|
||||
context.selectedIDs = function() {
|
||||
if (mode && mode.selectedIDs) {
|
||||
return mode.selectedIDs();
|
||||
@@ -219,15 +190,16 @@ window.iD = function () {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Behaviors */
|
||||
context.install = function(behavior) {
|
||||
context.surface().call(behavior);
|
||||
};
|
||||
|
||||
context.uninstall = function(behavior) {
|
||||
context.surface().call(behavior.off);
|
||||
};
|
||||
|
||||
|
||||
/* Copy/Paste */
|
||||
var copyIDs = [], copyGraph;
|
||||
context.copyGraph = function() { return copyGraph; };
|
||||
@@ -238,15 +210,14 @@ window.iD = function () {
|
||||
return context;
|
||||
};
|
||||
|
||||
/* Projection */
|
||||
context.projection = iD.geo.RawMercator();
|
||||
|
||||
/* Background */
|
||||
var background = iD.Background(context);
|
||||
var background;
|
||||
context.background = function() { return background; };
|
||||
|
||||
|
||||
/* Features */
|
||||
var features = iD.Features(context);
|
||||
var features;
|
||||
context.features = function() { return features; };
|
||||
context.hasHiddenConnections = function(id) {
|
||||
var graph = history.graph(),
|
||||
@@ -254,20 +225,13 @@ window.iD = function () {
|
||||
return features.hasHiddenConnections(entity, graph);
|
||||
};
|
||||
|
||||
|
||||
/* Map */
|
||||
var map = iD.Map(context);
|
||||
var map;
|
||||
context.map = function() { return map; };
|
||||
context.layers = function() { return map.layers; };
|
||||
context.surface = function() { return map.surface; };
|
||||
context.editable = function() { return map.editable(); };
|
||||
context.mouse = map.mouse;
|
||||
context.extent = map.extent;
|
||||
context.pan = map.pan;
|
||||
context.zoomIn = map.zoomIn;
|
||||
context.zoomOut = map.zoomOut;
|
||||
context.zoomInFurther = map.zoomInFurther;
|
||||
context.zoomOutFurther = map.zoomOutFurther;
|
||||
context.redrawEnable = map.redrawEnable;
|
||||
|
||||
context.surfaceRect = function() {
|
||||
// Work around a bug in Firefox.
|
||||
@@ -276,9 +240,9 @@ window.iD = function () {
|
||||
return context.surface().node().parentNode.getBoundingClientRect();
|
||||
};
|
||||
|
||||
/* Presets */
|
||||
var presets = iD.presets();
|
||||
|
||||
/* Presets */
|
||||
var presets;
|
||||
context.presets = function(_) {
|
||||
if (!arguments.length) return presets;
|
||||
presets.load(_);
|
||||
@@ -286,17 +250,28 @@ window.iD = function () {
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Imagery */
|
||||
context.imagery = function(_) {
|
||||
background.load(_);
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Container */
|
||||
var container, embed;
|
||||
context.container = function(_) {
|
||||
if (!arguments.length) return container;
|
||||
container = _;
|
||||
container.classed('id-container', true);
|
||||
return context;
|
||||
};
|
||||
context.embed = function(_) {
|
||||
if (!arguments.length) return embed;
|
||||
embed = _;
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Taginfo */
|
||||
var taginfo;
|
||||
@@ -306,13 +281,8 @@ window.iD = function () {
|
||||
return context;
|
||||
};
|
||||
|
||||
var embed = false;
|
||||
context.embed = function(_) {
|
||||
if (!arguments.length) return embed;
|
||||
embed = _;
|
||||
return context;
|
||||
};
|
||||
|
||||
/* Assets */
|
||||
var assetPath = '';
|
||||
context.assetPath = function(_) {
|
||||
if (!arguments.length) return assetPath;
|
||||
@@ -332,9 +302,63 @@ window.iD = function () {
|
||||
return assetMap[asset] || assetPath + asset;
|
||||
};
|
||||
|
||||
|
||||
/* Init */
|
||||
|
||||
context.projection = iD.geo.RawMercator();
|
||||
|
||||
locale = iD.detect().locale;
|
||||
if (locale && iD.data.locales.indexOf(locale) === -1) {
|
||||
locale = locale.split('-')[0];
|
||||
}
|
||||
|
||||
history = iD.History(context);
|
||||
context.graph = history.graph;
|
||||
context.changes = history.changes;
|
||||
context.intersects = history.intersects;
|
||||
|
||||
// Debounce save, since it's a synchronous localStorage write,
|
||||
// and history changes can happen frequently (e.g. when dragging).
|
||||
context.debouncedSave = _.debounce(context.save, 350);
|
||||
function withDebouncedSave(fn) {
|
||||
return function() {
|
||||
var result = fn.apply(history, arguments);
|
||||
context.debouncedSave();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
context.perform = withDebouncedSave(history.perform);
|
||||
context.replace = withDebouncedSave(history.replace);
|
||||
context.pop = withDebouncedSave(history.pop);
|
||||
context.overwrite = withDebouncedSave(history.overwrite);
|
||||
context.undo = withDebouncedSave(history.undo);
|
||||
context.redo = withDebouncedSave(history.redo);
|
||||
|
||||
ui = iD.ui(context);
|
||||
|
||||
connection = iD.Connection();
|
||||
|
||||
background = iD.Background(context);
|
||||
|
||||
features = iD.Features(context);
|
||||
|
||||
map = iD.Map(context);
|
||||
context.mouse = map.mouse;
|
||||
context.extent = map.extent;
|
||||
context.pan = map.pan;
|
||||
context.zoomIn = map.zoomIn;
|
||||
context.zoomOut = map.zoomOut;
|
||||
context.zoomInFurther = map.zoomInFurther;
|
||||
context.zoomOutFurther = map.zoomOutFurther;
|
||||
context.redrawEnable = map.redrawEnable;
|
||||
|
||||
presets = iD.presets();
|
||||
|
||||
return d3.rebind(context, dispatch, 'on');
|
||||
};
|
||||
|
||||
|
||||
iD.version = '1.8.5';
|
||||
|
||||
(function() {
|
||||
|
||||
@@ -4,7 +4,8 @@ iD.Background = function(context) {
|
||||
.projection(context.projection),
|
||||
gpxLayer = iD.GpxLayer(context, dispatch)
|
||||
.projection(context.projection),
|
||||
mapillaryLayer = iD.MapillaryLayer(context),
|
||||
mapillaryImageLayer,
|
||||
mapillarySignLayer,
|
||||
overlayLayers = [];
|
||||
|
||||
var backgroundSources;
|
||||
@@ -85,13 +86,43 @@ iD.Background = function(context) {
|
||||
|
||||
gpx.call(gpxLayer);
|
||||
|
||||
var mapillary = selection.selectAll('.layer-mapillary')
|
||||
.data([0]);
|
||||
|
||||
mapillary.enter().insert('div')
|
||||
.attr('class', 'layer-layer layer-mapillary');
|
||||
var mapillary = iD.services.mapillary,
|
||||
supportsMapillaryImages = !!mapillary,
|
||||
supportsMapillarySigns = !!mapillary && mapillary().signsSupported();
|
||||
|
||||
mapillary.call(mapillaryLayer);
|
||||
var mapillaryImages = selection.selectAll('.layer-mapillary-images')
|
||||
.data(supportsMapillaryImages ? [0] : []);
|
||||
|
||||
mapillaryImages.enter().insert('div')
|
||||
.attr('class', 'layer-layer layer-mapillary-images');
|
||||
|
||||
if (supportsMapillaryImages) {
|
||||
if (!mapillaryImageLayer) { mapillaryImageLayer = iD.MapillaryImageLayer(context); }
|
||||
mapillaryImages.call(mapillaryImageLayer);
|
||||
} else {
|
||||
mapillaryImageLayer = null;
|
||||
}
|
||||
|
||||
mapillaryImages.exit()
|
||||
.remove();
|
||||
|
||||
|
||||
var mapillarySigns = selection.selectAll('.layer-mapillary-signs')
|
||||
.data(supportsMapillarySigns ? [0] : []);
|
||||
|
||||
mapillarySigns.enter().insert('div')
|
||||
.attr('class', 'layer-layer layer-mapillary-signs');
|
||||
|
||||
if (supportsMapillarySigns) {
|
||||
if (!mapillarySignLayer) { mapillarySignLayer = iD.MapillarySignLayer(context); }
|
||||
mapillarySigns.call(mapillarySignLayer);
|
||||
} else {
|
||||
mapillarySignLayer = null;
|
||||
}
|
||||
|
||||
mapillarySigns.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
background.sources = function(extent) {
|
||||
@@ -103,7 +134,8 @@ iD.Background = function(context) {
|
||||
background.dimensions = function(_) {
|
||||
baseLayer.dimensions(_);
|
||||
gpxLayer.dimensions(_);
|
||||
mapillaryLayer.dimensions(_);
|
||||
if (mapillaryImageLayer) mapillaryImageLayer.dimensions(_);
|
||||
if (mapillarySignLayer) mapillarySignLayer.dimensions(_);
|
||||
|
||||
overlayLayers.forEach(function(layer) {
|
||||
layer.dimensions(_);
|
||||
@@ -172,12 +204,23 @@ iD.Background = function(context) {
|
||||
dispatch.change();
|
||||
};
|
||||
|
||||
background.showsMapillaryLayer = function() {
|
||||
return mapillaryLayer.enable();
|
||||
background.showsMapillaryImageLayer = function() {
|
||||
return mapillaryImageLayer && mapillaryImageLayer.enable();
|
||||
};
|
||||
|
||||
background.toggleMapillaryLayer = function() {
|
||||
mapillaryLayer.enable(!mapillaryLayer.enable());
|
||||
background.showsMapillarySignLayer = function() {
|
||||
return mapillarySignLayer && mapillarySignLayer.enable();
|
||||
};
|
||||
|
||||
background.toggleMapillaryImageLayer = function() {
|
||||
if (!mapillaryImageLayer) return;
|
||||
mapillaryImageLayer.enable(!mapillaryImageLayer.enable());
|
||||
dispatch.change();
|
||||
};
|
||||
|
||||
background.toggleMapillarySignLayer = function() {
|
||||
if (!mapillarySignLayer) return;
|
||||
mapillarySignLayer.enable(!mapillarySignLayer.enable());
|
||||
dispatch.change();
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ iD.Map = function(context) {
|
||||
supersurface = selection.append('div')
|
||||
.attr('id', 'supersurface');
|
||||
|
||||
// Mapillary streetsigns require supersurface transform to have
|
||||
// a value in order to do correct foreignObject positioning in Chrome
|
||||
iD.util.setTransform(supersurface, 0, 0);
|
||||
|
||||
// Need a wrapper div because Opera can't cope with an absolutely positioned
|
||||
// SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
|
||||
var dataLayer = supersurface.append('div')
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
iD.MapillaryImageLayer = function(context) {
|
||||
var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
|
||||
enabled = false,
|
||||
minZoom = 12,
|
||||
layer = d3.select(null),
|
||||
_mapillary;
|
||||
|
||||
function getMapillary() {
|
||||
if (iD.services.mapillary && !_mapillary) {
|
||||
_mapillary = iD.services.mapillary().on('loadedImages', debouncedRedraw);
|
||||
} else if (!iD.services.mapillary && _mapillary) {
|
||||
_mapillary = null;
|
||||
}
|
||||
return _mapillary;
|
||||
}
|
||||
|
||||
function showThumbnail(image) {
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
|
||||
var thumb = mapillary.selectedThumbnail(),
|
||||
posX = context.projection(image.loc)[0],
|
||||
width = layer.dimensions()[0],
|
||||
position = (posX < width / 2) ? 'right' : 'left';
|
||||
|
||||
if (thumb) {
|
||||
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
|
||||
.classed('selected', function(d) { return d.key === thumb.key; });
|
||||
}
|
||||
|
||||
mapillary.showThumbnail(image.key, position);
|
||||
}
|
||||
|
||||
function hideThumbnail() {
|
||||
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
|
||||
.classed('selected', false);
|
||||
|
||||
var mapillary = getMapillary();
|
||||
if (mapillary) {
|
||||
mapillary.hideThumbnail();
|
||||
}
|
||||
}
|
||||
|
||||
function showLayer() {
|
||||
editOn();
|
||||
layer
|
||||
.style('opacity', 0)
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 1)
|
||||
.each('end', debouncedRedraw);
|
||||
}
|
||||
|
||||
function hideLayer() {
|
||||
debouncedRedraw.cancel();
|
||||
hideThumbnail();
|
||||
layer
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0)
|
||||
.each('end', editOff);
|
||||
}
|
||||
|
||||
function editOn() {
|
||||
layer.style('display', 'block');
|
||||
}
|
||||
|
||||
function editOff() {
|
||||
layer.selectAll('.viewfield-group').remove();
|
||||
layer.style('display', 'none');
|
||||
}
|
||||
|
||||
function transform(d) {
|
||||
var t = iD.svg.PointTransform(context.projection)(d);
|
||||
if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
|
||||
return t;
|
||||
}
|
||||
|
||||
function drawMarkers() {
|
||||
var mapillary = getMapillary(),
|
||||
data = (mapillary ? mapillary.images(context.projection, layer.dimensions()) : []);
|
||||
|
||||
var markers = layer.selectAll('.viewfield-group')
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
// Enter
|
||||
var enter = markers.enter()
|
||||
.append('g')
|
||||
.attr('class', 'viewfield-group');
|
||||
|
||||
enter.append('path')
|
||||
.attr('class', 'viewfield')
|
||||
.attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
|
||||
.attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
|
||||
|
||||
enter.append('circle')
|
||||
.attr('dx', '0')
|
||||
.attr('dy', '0')
|
||||
.attr('r', '6');
|
||||
|
||||
// Exit
|
||||
markers.exit()
|
||||
.remove();
|
||||
|
||||
// Update
|
||||
markers
|
||||
.attr('transform', transform);
|
||||
}
|
||||
|
||||
function render(selection) {
|
||||
var mapillary = getMapillary();
|
||||
|
||||
layer = selection.selectAll('svg')
|
||||
.data(mapillary ? [0] : []);
|
||||
|
||||
layer.enter()
|
||||
.append('svg')
|
||||
.style('display', enabled ? 'block' : 'none')
|
||||
.dimensions(context.map().dimensions())
|
||||
.on('click', function() { // deselect/select
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
var d = d3.event.target.__data__,
|
||||
thumb = mapillary.selectedThumbnail();
|
||||
if (thumb && thumb.key === d.key) {
|
||||
hideThumbnail();
|
||||
} else {
|
||||
mapillary.selectedThumbnail(d);
|
||||
context.map().centerEase(d.loc);
|
||||
showThumbnail(d);
|
||||
}
|
||||
})
|
||||
.on('mouseover', function() {
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
showThumbnail(d3.event.target.__data__);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
var thumb = mapillary.selectedThumbnail();
|
||||
if (thumb) {
|
||||
showThumbnail(thumb);
|
||||
} else {
|
||||
hideThumbnail();
|
||||
}
|
||||
});
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
|
||||
if (enabled) {
|
||||
if (mapillary && ~~context.map().zoom() >= minZoom) {
|
||||
editOn();
|
||||
drawMarkers();
|
||||
mapillary.loadImages(context.projection, layer.dimensions());
|
||||
} else {
|
||||
editOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render.enable = function(_) {
|
||||
if (!arguments.length) return enabled;
|
||||
enabled = _;
|
||||
if (enabled) {
|
||||
showLayer();
|
||||
} else {
|
||||
hideLayer();
|
||||
}
|
||||
return render;
|
||||
};
|
||||
|
||||
render.dimensions = function(_) {
|
||||
if (layer.empty()) return null;
|
||||
if (!arguments.length) return layer.dimensions();
|
||||
layer.dimensions(_);
|
||||
return render;
|
||||
};
|
||||
|
||||
return render;
|
||||
};
|
||||
@@ -1,159 +0,0 @@
|
||||
iD.MapillaryLayer = function (context) {
|
||||
var enable = false,
|
||||
currentImage,
|
||||
svg, div, request;
|
||||
|
||||
function show(image) {
|
||||
svg.selectAll('g')
|
||||
.classed('selected', function(d) {
|
||||
return currentImage && d.key === currentImage.key;
|
||||
});
|
||||
|
||||
div.classed('hidden', false)
|
||||
.classed('temp', image !== currentImage);
|
||||
|
||||
div.selectAll('img')
|
||||
.attr('src', 'https://d1cuyjsrcm0gby.cloudfront.net/' + image.key + '/thumb-320.jpg');
|
||||
|
||||
div.selectAll('a')
|
||||
.attr('href', 'https://www.mapillary.com/map/im/' + image.key);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
currentImage = undefined;
|
||||
|
||||
svg.selectAll('g')
|
||||
.classed('selected', false);
|
||||
|
||||
div.classed('hidden', true);
|
||||
}
|
||||
|
||||
function transform(image) {
|
||||
var t = 'translate(' + context.projection(image.loc) + ')';
|
||||
if (image.ca) t += 'rotate(' + image.ca + ',0,0)';
|
||||
return t;
|
||||
}
|
||||
|
||||
function render(selection) {
|
||||
svg = selection.selectAll('svg')
|
||||
.data([0]);
|
||||
|
||||
svg.enter().append('svg')
|
||||
.on('click', function() {
|
||||
var image = d3.event.target.__data__;
|
||||
if (currentImage === image) {
|
||||
hide();
|
||||
} else {
|
||||
currentImage = image;
|
||||
show(image);
|
||||
}
|
||||
})
|
||||
.on('mouseover', function() {
|
||||
show(d3.event.target.__data__);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
if (currentImage) {
|
||||
show(currentImage);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
svg.style('display', enable ? 'block' : 'none');
|
||||
|
||||
div = context.container().selectAll('.mapillary-image')
|
||||
.data([0]);
|
||||
|
||||
var enter = div.enter().append('div')
|
||||
.attr('class', 'mapillary-image');
|
||||
|
||||
enter.append('button')
|
||||
.on('click', hide)
|
||||
.append('div')
|
||||
.call(iD.svg.Icon('#icon-close'));
|
||||
|
||||
enter.append('img');
|
||||
|
||||
enter
|
||||
.append('a')
|
||||
.attr('class', 'link')
|
||||
.attr('target', '_blank')
|
||||
.call(iD.svg.Icon('#icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('mapillary.view_on_mapillary'));
|
||||
|
||||
if (!enable) {
|
||||
hide();
|
||||
|
||||
svg.selectAll('g')
|
||||
.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update existing images while waiting for new ones to load.
|
||||
svg.selectAll('g')
|
||||
.attr('transform', transform);
|
||||
|
||||
var extent = context.map().extent();
|
||||
|
||||
if (request)
|
||||
request.abort();
|
||||
|
||||
request = d3.json('https://a.mapillary.com/v2/search/s/geojson?client_id=NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzoxNjQ3MDY4ZTUxY2QzNGI2&min_lat=' +
|
||||
extent[0][1] + '&max_lat=' + extent[1][1] + '&min_lon=' +
|
||||
extent[0][0] + '&max_lon=' + extent[1][0] + '&max_results=100&geojson=true',
|
||||
function (error, data) {
|
||||
if (error) return;
|
||||
|
||||
var images = [];
|
||||
|
||||
for (var i = 0; i < data.features.length; i++) {
|
||||
var sequence = data.features[i];
|
||||
for (var j = 0; j < sequence.geometry.coordinates.length; j++) {
|
||||
images.push({
|
||||
key: sequence.properties.keys[j],
|
||||
ca: sequence.properties.cas[j],
|
||||
loc: sequence.geometry.coordinates[j]
|
||||
});
|
||||
if (images.length >= 1000) break;
|
||||
}
|
||||
}
|
||||
|
||||
var g = svg.selectAll('g')
|
||||
.data(images, function(d) { return d.key; });
|
||||
|
||||
var enter = g.enter().append('g')
|
||||
.attr('class', 'image');
|
||||
|
||||
enter.append('path')
|
||||
.attr('class', 'viewfield')
|
||||
.attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
|
||||
.attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
|
||||
|
||||
enter.append('circle')
|
||||
.attr('dx', '0')
|
||||
.attr('dy', '0')
|
||||
.attr('r', '6');
|
||||
|
||||
g.attr('transform', transform);
|
||||
|
||||
g.exit()
|
||||
.remove();
|
||||
});
|
||||
}
|
||||
|
||||
render.enable = function(_) {
|
||||
if (!arguments.length) return enable;
|
||||
enable = _;
|
||||
return render;
|
||||
};
|
||||
|
||||
render.dimensions = function(_) {
|
||||
if (!arguments.length) return svg.dimensions();
|
||||
svg.dimensions(_);
|
||||
return render;
|
||||
};
|
||||
|
||||
return render;
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
iD.MapillarySignLayer = function(context) {
|
||||
var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
|
||||
enabled = false,
|
||||
minZoom = 12,
|
||||
layer = d3.select(null),
|
||||
_mapillary;
|
||||
|
||||
function getMapillary() {
|
||||
if (iD.services.mapillary && !_mapillary) {
|
||||
_mapillary = iD.services.mapillary().on('loadedSigns', debouncedRedraw);
|
||||
} else if (!iD.services.mapillary && _mapillary) {
|
||||
_mapillary = null;
|
||||
}
|
||||
return _mapillary;
|
||||
}
|
||||
|
||||
function showThumbnail(image) {
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
|
||||
var thumb = mapillary.selectedThumbnail(),
|
||||
posX = context.projection(image.loc)[0],
|
||||
width = layer.dimensions()[0],
|
||||
position = (posX < width / 2) ? 'right' : 'left';
|
||||
|
||||
if (thumb) {
|
||||
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
|
||||
.classed('selected', function(d) { return d.key === thumb.key; });
|
||||
}
|
||||
|
||||
mapillary.showThumbnail(image.key, position);
|
||||
}
|
||||
|
||||
function hideThumbnail() {
|
||||
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
|
||||
.classed('selected', false);
|
||||
|
||||
var mapillary = getMapillary();
|
||||
if (mapillary) {
|
||||
mapillary.hideThumbnail();
|
||||
}
|
||||
}
|
||||
|
||||
function showLayer() {
|
||||
editOn();
|
||||
debouncedRedraw();
|
||||
}
|
||||
|
||||
function hideLayer() {
|
||||
debouncedRedraw.cancel();
|
||||
hideThumbnail();
|
||||
editOff();
|
||||
}
|
||||
|
||||
function editOn() {
|
||||
layer.style('display', 'block');
|
||||
}
|
||||
|
||||
function editOff() {
|
||||
layer.selectAll('.icon-sign').remove();
|
||||
layer.style('display', 'none');
|
||||
}
|
||||
|
||||
function drawSigns() {
|
||||
var mapillary = getMapillary(),
|
||||
data = (mapillary ? mapillary.signs(context.projection, layer.dimensions()) : []);
|
||||
|
||||
var signs = layer.select('.mapillary-sign-offset')
|
||||
.selectAll('.icon-sign')
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
// Enter
|
||||
var enter = signs.enter()
|
||||
.append('foreignObject')
|
||||
.attr('class', 'icon-sign')
|
||||
.attr('width', '32px') // for Firefox
|
||||
.attr('height', '32px'); // for Firefox
|
||||
|
||||
enter
|
||||
.append('xhtml:body')
|
||||
.html(mapillary.signHTML);
|
||||
|
||||
enter
|
||||
.on('click', function(d) { // deselect/select
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
var thumb = mapillary.selectedThumbnail();
|
||||
if (thumb && thumb.key === d.key) {
|
||||
hideThumbnail();
|
||||
} else {
|
||||
mapillary.selectedThumbnail(d);
|
||||
context.map().centerEase(d.loc);
|
||||
showThumbnail(d);
|
||||
}
|
||||
})
|
||||
.on('mouseover', showThumbnail)
|
||||
.on('mouseout', function() {
|
||||
var mapillary = getMapillary();
|
||||
if (!mapillary) return;
|
||||
var thumb = mapillary.selectedThumbnail();
|
||||
if (thumb) {
|
||||
showThumbnail(thumb);
|
||||
} else {
|
||||
hideThumbnail();
|
||||
}
|
||||
});
|
||||
|
||||
// Exit
|
||||
signs.exit()
|
||||
.remove();
|
||||
|
||||
// Update
|
||||
signs
|
||||
.attr('transform', iD.svg.PointTransform(context.projection));
|
||||
}
|
||||
|
||||
function render(selection) {
|
||||
var mapillary = getMapillary();
|
||||
|
||||
layer = selection.selectAll('svg')
|
||||
.data(mapillary ? [0] : []);
|
||||
|
||||
layer.enter()
|
||||
.append('svg')
|
||||
.style('display', enabled ? 'block' : 'none')
|
||||
.dimensions(context.map().dimensions())
|
||||
.append('g')
|
||||
.attr('class', 'mapillary-sign-offset')
|
||||
.attr('transform', 'translate(-16, -16)'); // center signs on loc
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
|
||||
if (enabled) {
|
||||
if (mapillary && ~~context.map().zoom() >= minZoom) {
|
||||
editOn();
|
||||
drawSigns();
|
||||
mapillary.loadSigns(context, context.projection, layer.dimensions());
|
||||
} else {
|
||||
editOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render.enable = function(_) {
|
||||
if (!arguments.length) return enabled;
|
||||
enabled = _;
|
||||
if (enabled) {
|
||||
showLayer();
|
||||
} else {
|
||||
hideLayer();
|
||||
}
|
||||
return render;
|
||||
};
|
||||
|
||||
render.dimensions = function(_) {
|
||||
if (layer.empty()) return null;
|
||||
if (!arguments.length) return layer.dimensions();
|
||||
layer.dimensions(_);
|
||||
return render;
|
||||
};
|
||||
|
||||
return render;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
iD.services = {};
|
||||
@@ -0,0 +1,274 @@
|
||||
iD.services.mapillary = function() {
|
||||
var mapillary = {},
|
||||
dispatch = d3.dispatch('loadedImages', 'loadedSigns'),
|
||||
apibase = 'https://a.mapillary.com/v2/',
|
||||
urlImage = 'https://www.mapillary.com/map/im/',
|
||||
urlThumb = 'https://d1cuyjsrcm0gby.cloudfront.net/',
|
||||
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
|
||||
maxResults = 1000,
|
||||
tileZoom = 14;
|
||||
|
||||
|
||||
function loadSignDefs(context) {
|
||||
if (!iD.services.mapillary.sign_defs) {
|
||||
iD.services.mapillary.sign_defs = {};
|
||||
_.each(['au', 'br', 'ca', 'de', 'us'], function(region) {
|
||||
d3.json(context.assetPath() + 'traffico/string-maps/' + region + '-map.json', function(err, data) {
|
||||
if (err) return;
|
||||
if (region === 'de') region = 'eu';
|
||||
iD.services.mapillary.sign_defs[region] = data;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function abortRequest(i) {
|
||||
i.abort();
|
||||
}
|
||||
|
||||
function getTiles(projection, dimensions) {
|
||||
var s = projection.scale() * 2 * Math.PI,
|
||||
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
|
||||
ts = 256 * Math.pow(2, z - tileZoom),
|
||||
origin = [
|
||||
s / 2 - projection.translate()[0],
|
||||
s / 2 - projection.translate()[1]];
|
||||
|
||||
return d3.geo.tile()
|
||||
.scaleExtent([tileZoom, tileZoom])
|
||||
.scale(s)
|
||||
.size(dimensions)
|
||||
.translate(projection.translate())()
|
||||
.map(function(tile) {
|
||||
var x = tile[0] * ts - origin[0],
|
||||
y = tile[1] * ts - origin[1];
|
||||
|
||||
return {
|
||||
id: tile.toString(),
|
||||
extent: iD.geo.Extent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y]))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadTiles(which, url, projection, dimensions) {
|
||||
var tiles = getTiles(projection, dimensions);
|
||||
|
||||
_.filter(which.inflight, function(v, k) {
|
||||
var wanted = _.find(tiles, function(tile) { return k === (tile.id + ',0'); });
|
||||
if (!wanted) delete which.inflight[k];
|
||||
return !wanted;
|
||||
}).map(abortRequest);
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
loadTilePage(which, url, tile, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function loadTilePage(which, url, tile, page) {
|
||||
var cache = iD.services.mapillary.cache[which],
|
||||
id = tile.id + ',' + String(page),
|
||||
rect = tile.extent.rectangle();
|
||||
|
||||
if (cache.loaded[id] || cache.inflight[id]) return;
|
||||
|
||||
cache.inflight[id] = d3.json(url +
|
||||
iD.util.qsString({
|
||||
geojson: 'true',
|
||||
limit: maxResults,
|
||||
page: page,
|
||||
client_id: clientId,
|
||||
min_lon: rect[0],
|
||||
min_lat: rect[1],
|
||||
max_lon: rect[2],
|
||||
max_lat: rect[3]
|
||||
}), function(err, data) {
|
||||
cache.loaded[id] = true;
|
||||
delete cache.inflight[id];
|
||||
if (err || !data.features || !data.features.length) return;
|
||||
|
||||
var features = [],
|
||||
feature, loc, d;
|
||||
|
||||
for (var i = 0; i < data.features.length; i++) {
|
||||
feature = data.features[i];
|
||||
loc = feature.geometry.coordinates;
|
||||
d = { key: feature.properties.key, loc: loc };
|
||||
if (which === 'images') d.ca = feature.properties.ca;
|
||||
if (which === 'signs') d.signs = feature.properties.rects;
|
||||
|
||||
features.push([loc[0], loc[1], loc[0], loc[1], d]);
|
||||
}
|
||||
|
||||
cache.rtree.load(features);
|
||||
|
||||
if (which === 'images') dispatch.loadedImages();
|
||||
if (which === 'signs') dispatch.loadedSigns();
|
||||
|
||||
if (data.features.length === maxResults) {
|
||||
loadTilePage(which, url, tile, ++page);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
mapillary.loadImages = function(projection, dimensions) {
|
||||
var url = apibase + 'search/im/geojson?';
|
||||
loadTiles('images', url, projection, dimensions);
|
||||
};
|
||||
|
||||
mapillary.loadSigns = function(context, projection, dimensions) {
|
||||
var url = apibase + 'search/im/geojson/or?';
|
||||
loadSignDefs(context);
|
||||
loadTiles('signs', url, projection, dimensions);
|
||||
};
|
||||
|
||||
|
||||
// partition viewport into `psize` x `psize` regions
|
||||
function partitionViewport(psize, projection, dimensions) {
|
||||
psize = psize || 16;
|
||||
var cols = d3.range(0, dimensions[0], psize),
|
||||
rows = d3.range(0, dimensions[1], psize),
|
||||
partitions = [];
|
||||
|
||||
rows.forEach(function(y) {
|
||||
cols.forEach(function(x) {
|
||||
var min = [x, y + psize],
|
||||
max = [x + psize, y];
|
||||
partitions.push(
|
||||
iD.geo.Extent(projection.invert(min), projection.invert(max)));
|
||||
});
|
||||
});
|
||||
|
||||
return partitions;
|
||||
}
|
||||
|
||||
// no more than `limit` results per partition.
|
||||
function searchLimited(psize, limit, projection, dimensions, rtree) {
|
||||
limit = limit || 3;
|
||||
|
||||
var partitions = partitionViewport(psize, projection, dimensions);
|
||||
return _.flatten(_.compact(_.map(partitions, function(extent) {
|
||||
return rtree.search(extent.rectangle())
|
||||
.slice(0, limit)
|
||||
.map(function(d) { return d[4]; });
|
||||
})));
|
||||
}
|
||||
|
||||
mapillary.images = function(projection, dimensions) {
|
||||
var psize = 16, limit = 3;
|
||||
return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.images.rtree);
|
||||
};
|
||||
|
||||
mapillary.signs = function(projection, dimensions) {
|
||||
var psize = 32, limit = 3;
|
||||
return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.signs.rtree);
|
||||
};
|
||||
|
||||
mapillary.signsSupported = function() {
|
||||
var detected = iD.detect();
|
||||
return (!(detected.ie || detected.browser.toLowerCase() === 'safari'));
|
||||
};
|
||||
|
||||
mapillary.signHTML = function(d) {
|
||||
if (!iD.services.mapillary.sign_defs) return;
|
||||
|
||||
var detectionPackage = d.signs[0].package,
|
||||
type = d.signs[0].type,
|
||||
country = detectionPackage.split('_')[1];
|
||||
return iD.services.mapillary.sign_defs[country][type];
|
||||
};
|
||||
|
||||
mapillary.showThumbnail = function(imageKey, position) {
|
||||
if (!imageKey) return;
|
||||
|
||||
var positionClass = {
|
||||
'ar': (position !== 'left'),
|
||||
'al': (position === 'left')
|
||||
};
|
||||
|
||||
var thumbnail = d3.select('#content').selectAll('.mapillary-image')
|
||||
.data([0]);
|
||||
|
||||
// Enter
|
||||
var enter = thumbnail.enter().append('div')
|
||||
.attr('class', 'mapillary-image ar');
|
||||
|
||||
enter.append('button')
|
||||
.on('click', function () {
|
||||
mapillary.hideThumbnail();
|
||||
})
|
||||
.append('div')
|
||||
.call(iD.svg.Icon('#icon-close'));
|
||||
|
||||
enter.append('img');
|
||||
|
||||
enter.append('a')
|
||||
.attr('class', 'link ar')
|
||||
.attr('target', '_blank')
|
||||
.call(iD.svg.Icon('#icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('mapillary.view_on_mapillary'));
|
||||
|
||||
// Update
|
||||
thumbnail.selectAll('img')
|
||||
.attr('src', urlThumb + imageKey + '/thumb-320.jpg');
|
||||
|
||||
var link = thumbnail.selectAll('a')
|
||||
.attr('href', urlImage + imageKey);
|
||||
|
||||
if (position) {
|
||||
thumbnail.classed(positionClass);
|
||||
link.classed(positionClass);
|
||||
}
|
||||
|
||||
thumbnail
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', 1);
|
||||
};
|
||||
|
||||
mapillary.hideThumbnail = function() {
|
||||
if (iD.services.mapillary) {
|
||||
iD.services.mapillary.thumb = null;
|
||||
}
|
||||
d3.select('#content').selectAll('.mapillary-image')
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', 0)
|
||||
.remove();
|
||||
};
|
||||
|
||||
mapillary.selectedThumbnail = function(d) {
|
||||
if (!iD.services.mapillary) return null;
|
||||
if (!arguments.length) return iD.services.mapillary.thumb;
|
||||
iD.services.mapillary.thumb = d;
|
||||
};
|
||||
|
||||
mapillary.reset = function() {
|
||||
var cache = iD.services.mapillary.cache;
|
||||
|
||||
if (cache) {
|
||||
_.forEach(cache.images.inflight, abortRequest);
|
||||
_.forEach(cache.signs.inflight, abortRequest);
|
||||
}
|
||||
|
||||
iD.services.mapillary.cache = {
|
||||
images: { inflight: {}, loaded: {}, rtree: rbush() },
|
||||
signs: { inflight: {}, loaded: {}, rtree: rbush() }
|
||||
};
|
||||
|
||||
iD.services.mapillary.thumb = null;
|
||||
|
||||
return mapillary;
|
||||
};
|
||||
|
||||
|
||||
if (!iD.services.mapillary.cache) {
|
||||
mapillary.reset();
|
||||
}
|
||||
|
||||
return d3.rebind(mapillary, dispatch, 'on');
|
||||
};
|
||||
@@ -1,15 +1,11 @@
|
||||
iD.countryCode = function() {
|
||||
var countryCode = {},
|
||||
iD.services.nominatim = function() {
|
||||
var nominatim = {},
|
||||
endpoint = 'https://nominatim.openstreetmap.org/reverse?';
|
||||
|
||||
if (!iD.countryCode.cache) {
|
||||
iD.countryCode.cache = rbush();
|
||||
}
|
||||
|
||||
var cache = iD.countryCode.cache;
|
||||
|
||||
countryCode.search = function(location, callback) {
|
||||
var countryCodes = cache.search([location[0], location[1], location[0], location[1]]);
|
||||
nominatim.countryCode = function(location, callback) {
|
||||
var cache = iD.services.nominatim.cache,
|
||||
countryCodes = cache.search([location[0], location[1], location[0], location[1]]);
|
||||
|
||||
if (countryCodes.length > 0)
|
||||
return callback(null, countryCodes[0][4]);
|
||||
@@ -28,11 +24,21 @@ iD.countryCode = function() {
|
||||
|
||||
var extent = iD.geo.Extent(location).padByMeters(1000);
|
||||
|
||||
cache.insert([extent[0][0], extent[0][1], extent[1][0], extent[1][1], result.address.country_code]);
|
||||
cache.insert(extent.rectangle().concat(result.address.country_code));
|
||||
|
||||
callback(null, result.address.country_code);
|
||||
});
|
||||
};
|
||||
|
||||
return countryCode;
|
||||
nominatim.reset = function() {
|
||||
iD.services.nominatim.cache = rbush();
|
||||
return nominatim;
|
||||
};
|
||||
|
||||
|
||||
if (!iD.services.nominatim.cache) {
|
||||
nominatim.reset();
|
||||
}
|
||||
|
||||
return nominatim;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
iD.taginfo = function() {
|
||||
iD.services.taginfo = function() {
|
||||
var taginfo = {},
|
||||
endpoint = 'https://taginfo.openstreetmap.org/api/4/',
|
||||
tag_sorts = {
|
||||
@@ -14,11 +14,6 @@ iD.taginfo = function() {
|
||||
line: 'ways'
|
||||
};
|
||||
|
||||
if (!iD.taginfo.cache) {
|
||||
iD.taginfo.cache = {};
|
||||
}
|
||||
|
||||
var cache = iD.taginfo.cache;
|
||||
|
||||
function sets(parameters, n, o) {
|
||||
if (parameters.geometry && o[parameters.geometry]) {
|
||||
@@ -68,6 +63,8 @@ iD.taginfo = function() {
|
||||
var debounced = _.debounce(d3.json, 100, true);
|
||||
|
||||
function request(url, debounce, callback) {
|
||||
var cache = iD.services.taginfo.cache;
|
||||
|
||||
if (cache[url]) {
|
||||
callback(null, cache[url]);
|
||||
} else if (debounce) {
|
||||
@@ -132,5 +129,15 @@ iD.taginfo = function() {
|
||||
return taginfo;
|
||||
};
|
||||
|
||||
taginfo.reset = function() {
|
||||
iD.services.taginfo.cache = {};
|
||||
return taginfo;
|
||||
};
|
||||
|
||||
|
||||
if (!iD.services.taginfo.cache) {
|
||||
taginfo.reset();
|
||||
}
|
||||
|
||||
return taginfo;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
iD.wikipedia = function() {
|
||||
iD.services.wikipedia = function() {
|
||||
var wiki = {},
|
||||
endpoint = 'https://en.wikipedia.org/w/api.php?';
|
||||
|
||||
|
||||
+174
-92
@@ -5,6 +5,7 @@ iD.ui.MapData = function(context) {
|
||||
fillDefault = context.storage('area-fill') || 'partial',
|
||||
fillSelected = fillDefault;
|
||||
|
||||
|
||||
function map_data(selection) {
|
||||
|
||||
function showsFeature(d) {
|
||||
@@ -42,16 +43,168 @@ iD.ui.MapData = function(context) {
|
||||
update();
|
||||
}
|
||||
|
||||
function clickMapillary() {
|
||||
context.background().toggleMapillaryLayer();
|
||||
function clickMapillaryImages() {
|
||||
context.background().toggleMapillaryImageLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
function clickMapillarySigns() {
|
||||
context.background().toggleMapillarySignLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
function drawMapillaryItems(selection) {
|
||||
var mapillary = iD.services.mapillary,
|
||||
supportsMapillaryImages = !!mapillary,
|
||||
supportsMapillarySigns = !!mapillary && mapillary().signsSupported();
|
||||
|
||||
var mapillaryList = selection
|
||||
.selectAll('.mapillary-list')
|
||||
.data([0]);
|
||||
|
||||
// Enter
|
||||
mapillaryList
|
||||
.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list mapillary-list');
|
||||
|
||||
var mapillaryImageLayerItem = mapillaryList
|
||||
.selectAll('.item-mapillary-images')
|
||||
.data(supportsMapillaryImages ? [0] : []);
|
||||
|
||||
var enterImages = mapillaryImageLayerItem.enter()
|
||||
.append('li')
|
||||
.attr('class', 'item-mapillary-images');
|
||||
|
||||
var labelImages = enterImages.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('mapillary_images.tooltip'))
|
||||
.placement('top'));
|
||||
|
||||
labelImages.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickMapillaryImages);
|
||||
|
||||
labelImages.append('span')
|
||||
.text(t('mapillary_images.title'));
|
||||
|
||||
|
||||
var mapillarySignLayerItem = mapillaryList
|
||||
.selectAll('.item-mapillary-signs')
|
||||
.data(supportsMapillarySigns ? [0] : []);
|
||||
|
||||
var enterSigns = mapillarySignLayerItem.enter()
|
||||
.append('li')
|
||||
.attr('class', 'item-mapillary-signs');
|
||||
|
||||
var labelSigns = enterSigns.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('mapillary_signs.tooltip'))
|
||||
.placement('top'));
|
||||
|
||||
labelSigns.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickMapillarySigns);
|
||||
|
||||
labelSigns.append('span')
|
||||
.text(t('mapillary_signs.title'));
|
||||
|
||||
// Update
|
||||
var showsMapillaryImages = supportsMapillaryImages && context.background().showsMapillaryImageLayer(),
|
||||
showsMapillarySigns = supportsMapillarySigns && context.background().showsMapillarySignLayer();
|
||||
|
||||
mapillaryImageLayerItem
|
||||
.classed('active', showsMapillaryImages)
|
||||
.selectAll('input')
|
||||
.property('checked', showsMapillaryImages);
|
||||
|
||||
mapillarySignLayerItem
|
||||
.classed('active', showsMapillarySigns)
|
||||
.selectAll('input')
|
||||
.property('checked', showsMapillarySigns);
|
||||
|
||||
// Exit
|
||||
mapillaryImageLayerItem.exit()
|
||||
.remove();
|
||||
mapillarySignLayerItem.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
|
||||
function drawGpxItem(selection) {
|
||||
var supportsGpx = iD.detect().filedrop,
|
||||
gpxLayerItem = selection
|
||||
.selectAll('.layer-gpx')
|
||||
.data(supportsGpx ? [0] : []);
|
||||
|
||||
// Enter
|
||||
var enter = gpxLayerItem.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-gpx')
|
||||
.append('li')
|
||||
.classed('layer-toggle-gpx', true);
|
||||
|
||||
enter.append('button')
|
||||
.attr('class', 'layer-extent')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.zoom'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
context.background().zoomToGpxLayer();
|
||||
})
|
||||
.call(iD.svg.Icon('#icon-search'));
|
||||
|
||||
enter.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.browse'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
context.background().gpxLayerFiles(d3.event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.call(iD.svg.Icon('#icon-geolocate'));
|
||||
|
||||
var labelGpx = enter.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.drag_drop'))
|
||||
.placement('top'));
|
||||
|
||||
labelGpx.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickGpx);
|
||||
|
||||
labelGpx.append('span')
|
||||
.text(t('gpx.local_layer'));
|
||||
|
||||
// Update
|
||||
var hasGpx = supportsGpx && context.background().hasGpxLayer(),
|
||||
showsGpx = supportsGpx && context.background().showsGpxLayer();
|
||||
|
||||
gpxLayerItem
|
||||
.classed('active', showsGpx)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasGpx)
|
||||
.property('checked', showsGpx);
|
||||
|
||||
// Exit
|
||||
gpxLayerItem.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
|
||||
function drawList(selection, data, type, name, change, active) {
|
||||
var items = selection.selectAll('li')
|
||||
.data(data);
|
||||
|
||||
//enter
|
||||
// Enter
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', 'layer')
|
||||
@@ -79,7 +232,7 @@ iD.ui.MapData = function(context) {
|
||||
label.append('span')
|
||||
.text(function(d) { return t(name + '.' + d + '.description'); });
|
||||
|
||||
//update
|
||||
// Update
|
||||
items
|
||||
.classed('active', active)
|
||||
.selectAll('input')
|
||||
@@ -88,32 +241,24 @@ iD.ui.MapData = function(context) {
|
||||
return (name === 'feature' && autoHiddenFeature(d));
|
||||
});
|
||||
|
||||
//exit
|
||||
// Exit
|
||||
items.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature);
|
||||
dataLayerContainer.call(drawMapillaryItems);
|
||||
dataLayerContainer.call(drawGpxItem);
|
||||
|
||||
fillList.call(drawList, fills, 'radio', 'area_fill', setFill, showsFill);
|
||||
|
||||
var hasGpx = context.background().hasGpxLayer(),
|
||||
showsGpx = context.background().showsGpxLayer(),
|
||||
showsMapillary = context.background().showsMapillaryLayer();
|
||||
|
||||
gpxLayerItem
|
||||
.classed('active', showsGpx)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasGpx)
|
||||
.property('checked', showsGpx);
|
||||
|
||||
mapillaryLayerItem
|
||||
.classed('active', showsMapillary)
|
||||
.selectAll('input')
|
||||
.property('checked', showsMapillary);
|
||||
featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature);
|
||||
}
|
||||
|
||||
function hidePanel() { setVisible(false); }
|
||||
function hidePanel() {
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
if (d3.event) d3.event.preventDefault();
|
||||
@@ -136,6 +281,7 @@ iD.ui.MapData = function(context) {
|
||||
shown = show;
|
||||
|
||||
if (show) {
|
||||
update();
|
||||
selection.on('mousedown.map_data-inside', function() {
|
||||
return d3.event.stopPropagation();
|
||||
});
|
||||
@@ -184,79 +330,15 @@ iD.ui.MapData = function(context) {
|
||||
.classed('expanded', true)
|
||||
.on('click', function() {
|
||||
var exp = d3.select(this).classed('expanded');
|
||||
layerContainer.style('display', exp ? 'none' : 'block');
|
||||
dataLayerContainer.style('display', exp ? 'none' : 'block');
|
||||
d3.select(this).classed('expanded', !exp);
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
var layerContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
var dataLayerContainer = content.append('div')
|
||||
.attr('class', 'data-data-layers')
|
||||
.style('display', 'block');
|
||||
|
||||
// mapillary
|
||||
var mapillaryLayerItem = layerContainer.append('ul')
|
||||
.attr('class', 'layer-list')
|
||||
.append('li');
|
||||
|
||||
var label = mapillaryLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('mapillary.tooltip'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickMapillary);
|
||||
|
||||
label.append('span')
|
||||
.text(t('mapillary.title'));
|
||||
|
||||
// gpx
|
||||
var gpxLayerItem = layerContainer.append('ul')
|
||||
.style('display', iD.detect().filedrop ? 'block' : 'none')
|
||||
.attr('class', 'layer-list')
|
||||
.append('li')
|
||||
.classed('layer-toggle-gpx', true);
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-extent')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.zoom'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
context.background().zoomToGpxLayer();
|
||||
})
|
||||
.call(iD.svg.Icon('#icon-search'));
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.browse'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
context.background().gpxLayerFiles(d3.event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.call(iD.svg.Icon('#icon-geolocate'));
|
||||
|
||||
label = gpxLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.drag_drop'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.property('disabled', true)
|
||||
.on('change', clickGpx);
|
||||
|
||||
label.append('span')
|
||||
.text(t('gpx.local_layer'));
|
||||
|
||||
|
||||
// area fills
|
||||
content.append('a')
|
||||
@@ -272,11 +354,11 @@ iD.ui.MapData = function(context) {
|
||||
});
|
||||
|
||||
var fillContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
.attr('class', 'data-area-fills')
|
||||
.style('display', 'none');
|
||||
|
||||
var fillList = fillContainer.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
.attr('class', 'layer-list layer-fill-list');
|
||||
|
||||
|
||||
// feature filters
|
||||
@@ -293,11 +375,11 @@ iD.ui.MapData = function(context) {
|
||||
});
|
||||
|
||||
var featureContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
.attr('class', 'data-feature-filters')
|
||||
.style('display', 'none');
|
||||
|
||||
var featureList = featureContainer.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
.attr('class', 'layer-list layer-feature-list');
|
||||
|
||||
|
||||
context.features()
|
||||
|
||||
@@ -109,7 +109,7 @@ iD.ui.preset.address = function(field, context) {
|
||||
var center = entity.extent(context.graph()).center(),
|
||||
addressFormat;
|
||||
|
||||
iD.countryCode().search(center, function (err, countryCode) {
|
||||
iD.services.nominatim().countryCode(center, function (err, countryCode) {
|
||||
addressFormat = _.find(iD.data.addressFormats, function (a) {
|
||||
return a && a.countryCodes && _.contains(a.countryCodes, countryCode);
|
||||
}) || _.first(iD.data.addressFormats);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
iD.ui.preset.localized = function(field, context) {
|
||||
var dispatch = d3.dispatch('change', 'input'),
|
||||
wikipedia = iD.wikipedia(),
|
||||
wikipedia = iD.services.wikipedia(),
|
||||
input, localizedInputs, wikiTitles,
|
||||
entity;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
iD.ui.preset.wikipedia = function(field, context) {
|
||||
var dispatch = d3.dispatch('change'),
|
||||
wikipedia = iD.wikipedia(),
|
||||
wikipedia = iD.services.wikipedia(),
|
||||
link, entity, lang, title;
|
||||
|
||||
function i(selection) {
|
||||
|
||||
+11
-2
@@ -1,8 +1,17 @@
|
||||
d3.selection.prototype.dimensions = function (dimensions) {
|
||||
if (!arguments.length) {
|
||||
var node = this.node();
|
||||
return [node.offsetWidth,
|
||||
node.offsetHeight];
|
||||
if (!node) return;
|
||||
|
||||
var prop = this.property('__dimensions__');
|
||||
if (!prop) {
|
||||
var cr = node.getBoundingClientRect();
|
||||
prop = [cr.width, cr.height];
|
||||
this.property('__dimensions__', prop);
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
this.property('__dimensions__', [dimensions[0], dimensions[1]]);
|
||||
return this.attr({width: dimensions[0], height: dimensions[1]});
|
||||
};
|
||||
|
||||
+110
-43
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
(c) 2013, Vladimir Agafonkin
|
||||
(c) 2015, Vladimir Agafonkin
|
||||
RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
|
||||
https://github.com/mourner/rbush
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function rbush(maxEntries, format) {
|
||||
|
||||
@@ -57,6 +58,33 @@ rbush.prototype = {
|
||||
return result;
|
||||
},
|
||||
|
||||
collides: function (bbox) {
|
||||
|
||||
var node = this.data,
|
||||
toBBox = this.toBBox;
|
||||
|
||||
if (!intersects(bbox, node.bbox)) return false;
|
||||
|
||||
var nodesToSearch = [],
|
||||
i, len, child, childBBox;
|
||||
|
||||
while (node) {
|
||||
for (i = 0, len = node.children.length; i < len; i++) {
|
||||
|
||||
child = node.children[i];
|
||||
childBBox = node.leaf ? toBBox(child) : child.bbox;
|
||||
|
||||
if (intersects(bbox, childBBox)) {
|
||||
if (node.leaf || contains(bbox, childBBox)) return true;
|
||||
nodesToSearch.push(child);
|
||||
}
|
||||
}
|
||||
node = nodesToSearch.pop();
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
load: function (data) {
|
||||
if (!(data && data.length)) return this;
|
||||
|
||||
@@ -180,13 +208,14 @@ rbush.prototype = {
|
||||
return result;
|
||||
},
|
||||
|
||||
_build: function (items, left, right, level, height) {
|
||||
_build: function (items, left, right, height) {
|
||||
|
||||
var N = right - left + 1,
|
||||
M = this._maxEntries,
|
||||
node;
|
||||
|
||||
if (N <= M) {
|
||||
// reached leaf level; return leaf
|
||||
node = {
|
||||
children: items.slice(left, right + 1),
|
||||
height: 1,
|
||||
@@ -197,7 +226,7 @@ rbush.prototype = {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!level) {
|
||||
if (!height) {
|
||||
// target height of the bulk-loaded tree
|
||||
height = Math.ceil(Math.log(N) / Math.log(M));
|
||||
|
||||
@@ -205,31 +234,33 @@ rbush.prototype = {
|
||||
M = Math.ceil(N / Math.pow(M, height - 1));
|
||||
}
|
||||
|
||||
// TODO eliminate recursion?
|
||||
|
||||
node = {
|
||||
children: [],
|
||||
height: height,
|
||||
bbox: null
|
||||
bbox: null,
|
||||
leaf: false
|
||||
};
|
||||
|
||||
// split the items into M mostly square tiles
|
||||
|
||||
var N2 = Math.ceil(N / M),
|
||||
N1 = N2 * Math.ceil(Math.sqrt(M)),
|
||||
i, j, right2, childNode;
|
||||
i, j, right2, right3;
|
||||
|
||||
multiSelect(items, left, right, N1, this.compareMinX);
|
||||
|
||||
// split the items into M mostly square tiles
|
||||
for (i = left; i <= right; i += N1) {
|
||||
|
||||
if (i + N1 <= right) partitionSort(items, i, right, i + N1, this.compareMinX);
|
||||
right2 = Math.min(i + N1 - 1, right);
|
||||
|
||||
multiSelect(items, i, right2, N2, this.compareMinY);
|
||||
|
||||
for (j = i; j <= right2; j += N2) {
|
||||
|
||||
if (j + N2 <= right2) partitionSort(items, j, right2, j + N2, this.compareMinY);
|
||||
right3 = Math.min(j + N2 - 1, right2);
|
||||
|
||||
// pack each entry recursively
|
||||
childNode = this._build(items, j, Math.min(j + N2 - 1, right2), level + 1, height - 1);
|
||||
node.children.push(childNode);
|
||||
node.children.push(this._build(items, j, right3, height - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +340,13 @@ rbush.prototype = {
|
||||
|
||||
this._chooseSplitAxis(node, m, M);
|
||||
|
||||
var splitIndex = this._chooseSplitIndex(node, m, M);
|
||||
|
||||
var newNode = {
|
||||
children: node.children.splice(this._chooseSplitIndex(node, m, M)),
|
||||
height: node.height
|
||||
children: node.children.splice(splitIndex, node.children.length - splitIndex),
|
||||
height: node.height,
|
||||
bbox: null,
|
||||
leaf: false
|
||||
};
|
||||
|
||||
if (node.leaf) newNode.leaf = true;
|
||||
@@ -327,7 +362,9 @@ rbush.prototype = {
|
||||
// split root node
|
||||
this.data = {
|
||||
children: [node, newNode],
|
||||
height: node.height + 1
|
||||
height: node.height + 1,
|
||||
bbox: null,
|
||||
leaf: false
|
||||
};
|
||||
calcBBox(this.data, this.toBBox);
|
||||
},
|
||||
@@ -442,6 +479,7 @@ rbush.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// calculate node's bbox from bboxes of its children
|
||||
function calcBBox(node, toBBox) {
|
||||
node.bbox = distBBox(node, 0, node.children.length, toBBox);
|
||||
@@ -459,7 +497,6 @@ function distBBox(node, k, p, toBBox) {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
|
||||
function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; }
|
||||
|
||||
function extend(a, b) {
|
||||
@@ -481,7 +518,7 @@ function enlargedArea(a, b) {
|
||||
(Math.max(b[3], a[3]) - Math.min(b[1], a[1]));
|
||||
}
|
||||
|
||||
function intersectionArea (a, b) {
|
||||
function intersectionArea(a, b) {
|
||||
var minX = Math.max(a[0], b[0]),
|
||||
minY = Math.max(a[1], b[1]),
|
||||
maxX = Math.min(a[2], b[2]),
|
||||
@@ -498,44 +535,74 @@ function contains(a, b) {
|
||||
b[3] <= a[3];
|
||||
}
|
||||
|
||||
function intersects (a, b) {
|
||||
function intersects(a, b) {
|
||||
return b[0] <= a[2] &&
|
||||
b[1] <= a[3] &&
|
||||
b[2] >= a[0] &&
|
||||
b[3] >= a[1];
|
||||
}
|
||||
|
||||
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
||||
// combines selection algorithm with binary divide & conquer approach
|
||||
|
||||
function partitionSort(arr, left, right, k, compare) {
|
||||
var pivot;
|
||||
function multiSelect(arr, left, right, n, compare) {
|
||||
var stack = [left, right],
|
||||
mid;
|
||||
|
||||
while (true) {
|
||||
pivot = Math.floor((left + right) / 2);
|
||||
pivot = partition(arr, left, right, pivot, compare);
|
||||
while (stack.length) {
|
||||
right = stack.pop();
|
||||
left = stack.pop();
|
||||
|
||||
if (k === pivot) break;
|
||||
else if (k < pivot) right = pivot - 1;
|
||||
else left = pivot + 1;
|
||||
if (right - left <= n) continue;
|
||||
|
||||
mid = left + Math.ceil((right - left) / n / 2) * n;
|
||||
select(arr, left, right, mid, compare);
|
||||
|
||||
stack.push(left, mid, mid, right);
|
||||
}
|
||||
|
||||
partition(arr, left, right, k, compare);
|
||||
}
|
||||
|
||||
function partition(arr, left, right, pivot, compare) {
|
||||
var k = left,
|
||||
value = arr[pivot];
|
||||
// Floyd-Rivest selection algorithm:
|
||||
// sort an array between left and right (inclusive) so that the smallest k elements come first (unordered)
|
||||
function select(arr, left, right, k, compare) {
|
||||
var n, i, z, s, sd, newLeft, newRight, t, j;
|
||||
|
||||
swap(arr, pivot, right);
|
||||
|
||||
for (var i = left; i < right; i++) {
|
||||
if (compare(arr[i], value) < 0) {
|
||||
swap(arr, k, i);
|
||||
k++;
|
||||
while (right > left) {
|
||||
if (right - left > 600) {
|
||||
n = right - left + 1;
|
||||
i = k - left + 1;
|
||||
z = Math.log(n);
|
||||
s = 0.5 * Math.exp(2 * z / 3);
|
||||
sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1);
|
||||
newLeft = Math.max(left, Math.floor(k - i * s / n + sd));
|
||||
newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd));
|
||||
select(arr, newLeft, newRight, k, compare);
|
||||
}
|
||||
}
|
||||
swap(arr, right, k);
|
||||
|
||||
return k;
|
||||
t = arr[k];
|
||||
i = left;
|
||||
j = right;
|
||||
|
||||
swap(arr, left, k);
|
||||
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
||||
|
||||
while (i < j) {
|
||||
swap(arr, i, j);
|
||||
i++;
|
||||
j--;
|
||||
while (compare(arr[i], t) < 0) i++;
|
||||
while (compare(arr[j], t) > 0) j--;
|
||||
}
|
||||
|
||||
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
||||
else {
|
||||
j++;
|
||||
swap(arr, j, right);
|
||||
}
|
||||
|
||||
if (j <= k) left = j + 1;
|
||||
if (k <= j) right = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function swap(arr, i, j) {
|
||||
@@ -546,9 +613,9 @@ function swap(arr, i, j) {
|
||||
|
||||
|
||||
// export as AMD/CommonJS module or global variable
|
||||
if (typeof define === 'function' && define.amd) define(function() { return rbush; });
|
||||
if (typeof define === 'function' && define.amd) define('rbush', function () { return rbush; });
|
||||
else if (typeof module !== 'undefined') module.exports = rbush;
|
||||
else if (typeof self !== 'undefined') self.rbush = rbush;
|
||||
else window.rbush = rbush;
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
+9
-5
@@ -38,7 +38,9 @@
|
||||
<script src='../js/id/id.js'></script>
|
||||
<script src='../js/id/util.js'></script>
|
||||
|
||||
<script src='../js/id/services/countrycode.js'></script>
|
||||
<script src='../js/id/services.js'></script>
|
||||
<script src='../js/id/services/mapillary.js'></script>
|
||||
<script src='../js/id/services/nominatim.js'></script>
|
||||
<script src='../js/id/services/taginfo.js'></script>
|
||||
<script src='../js/id/services/wikipedia.js'></script>
|
||||
|
||||
@@ -56,7 +58,8 @@
|
||||
<script src='../js/id/renderer/map.js'></script>
|
||||
<script src='../js/id/renderer/gpx_layer.js'></script>
|
||||
<script src='../js/id/renderer/tile_layer.js'></script>
|
||||
<script src='../js/id/renderer/mapillary_layer.js'></script>
|
||||
<script src='../js/id/renderer/mapillary_image_layer.js'></script>
|
||||
<script src='../js/id/renderer/mapillary_sign_layer.js'></script>
|
||||
|
||||
<script src="../js/id/svg.js"></script>
|
||||
<script src="../js/id/svg/areas.js"></script>
|
||||
@@ -261,6 +264,7 @@
|
||||
<script src="spec/actions/split.js"></script>
|
||||
<script src="spec/actions/unrestrict_turn.js"></script>
|
||||
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/geo/extent.js"></script>
|
||||
<script src="spec/geo/intersection.js"></script>
|
||||
<script src="spec/geo/multipolygon.js"></script>
|
||||
@@ -300,9 +304,9 @@
|
||||
<script src="spec/ui/preset/localized.js"></script>
|
||||
<script src="spec/ui/preset/wikipedia.js"></script>
|
||||
|
||||
<script src="spec/countrycode.js"></script>
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/taginfo.js"></script>
|
||||
<script src="spec/services/mapillary.js"></script>
|
||||
<script src="spec/services/nominatim.js"></script>
|
||||
<script src="spec/services/taginfo.js"></script>
|
||||
|
||||
<script src="spec/util.js"></script>
|
||||
<script src='spec/util/session_mutex.js'></script>
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<script src="spec/actions/straighten.js"></script>
|
||||
<script src="spec/actions/unrestrict_turn.js"></script>
|
||||
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/geo/extent.js"></script>
|
||||
<script src="spec/geo/intersection.js"></script>
|
||||
<script src="spec/geo/multipolygon.js"></script>
|
||||
@@ -98,9 +99,9 @@
|
||||
<script src="spec/ui/preset/localized.js"></script>
|
||||
<script src="spec/ui/preset/wikipedia.js"></script>
|
||||
|
||||
<script src="spec/countrycode.js"></script>
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/taginfo.js"></script>
|
||||
<script src="spec/services/mapillary.js"></script>
|
||||
<script src="spec/services/nominatim.js"></script>
|
||||
<script src="spec/services/taginfo.js"></script>
|
||||
|
||||
<script src="spec/util.js"></script>
|
||||
<script src='spec/util/session_mutex.js'></script>
|
||||
|
||||
+14
-1
@@ -57,7 +57,20 @@ describe("iD.geo.Extent", function () {
|
||||
|
||||
describe("#center", function () {
|
||||
it("returns the center point", function () {
|
||||
expect(iD.geo.Extent([0, 0], [5, 10]).center()).to.eql([2.5, 5]);
|
||||
expect(iD.geo.Extent([0, 0], [5, 10]).center()).to.eql([2.5, 5]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#rectangle", function () {
|
||||
it("returns the extent as a rectangle", function () {
|
||||
expect(iD.geo.Extent([0, 0], [5, 10]).rectangle()).to.eql([0, 0, 5, 10]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#polygon", function () {
|
||||
it("returns the extent as a polygon", function () {
|
||||
expect(iD.geo.Extent([0, 0], [5, 10]).polygon())
|
||||
.to.eql([[0, 0], [0, 10], [5, 10], [5, 0], [0, 0]]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
describe('iD.services.mapillary', function() {
|
||||
var dimensions = [64, 64],
|
||||
context, server, mapillary;
|
||||
|
||||
beforeEach(function() {
|
||||
context = iD();
|
||||
context.projection.scale(667544.214430109); // z14
|
||||
|
||||
server = sinon.fakeServer.create();
|
||||
mapillary = iD.services.mapillary();
|
||||
mapillary.reset();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
server.restore();
|
||||
});
|
||||
|
||||
|
||||
describe('Mapillary service', function() {
|
||||
it('Initializes cache one time', function() {
|
||||
var cache = iD.services.mapillary.cache;
|
||||
expect(cache).to.have.property('images');
|
||||
expect(cache).to.have.property('signs');
|
||||
|
||||
var mapillary2 = iD.services.mapillary();
|
||||
var cache2 = iD.services.mapillary.cache;
|
||||
expect(cache).to.equal(cache2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#loadImages', function() {
|
||||
it('fires loadedImages when images are loaded', function() {
|
||||
var spy = sinon.spy();
|
||||
mapillary.on('loadedImages', spy);
|
||||
mapillary.loadImages(context.projection, dimensions);
|
||||
|
||||
var match = /search\/im\/geojson/,
|
||||
features = [{
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { ca: 90, key: '0' }
|
||||
}],
|
||||
response = { type: 'FeatureCollection', features: features };
|
||||
|
||||
server.respondWith('GET', match,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
|
||||
server.respond();
|
||||
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('loads multiple pages of image results', function() {
|
||||
var spy = sinon.spy();
|
||||
mapillary.on('loadedImages', spy);
|
||||
mapillary.loadImages(context.projection, dimensions);
|
||||
|
||||
var features0 = [],
|
||||
features1 = [],
|
||||
i;
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
features0.push({
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { ca: 90, key: String(i) }
|
||||
});
|
||||
}
|
||||
for (i = 0; i < 500; i++) {
|
||||
features1.push({
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { ca: 90, key: String(1000 + i) }
|
||||
});
|
||||
}
|
||||
|
||||
var match0 = /page=0/,
|
||||
response0 = { type: 'FeatureCollection', features: features0 },
|
||||
match1 = /page=1/,
|
||||
response1 = { type: 'FeatureCollection', features: features1 }
|
||||
|
||||
server.respondWith('GET', match0,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]);
|
||||
server.respondWith('GET', match1,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]);
|
||||
server.respond();
|
||||
|
||||
expect(spy).to.have.been.calledTwice;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#loadSigns', function() {
|
||||
it('loads sign_defs', function() {
|
||||
mapillary.loadSigns(context, context.projection, dimensions);
|
||||
|
||||
var base = 'regulatory--maximum-speed-limit-65--',
|
||||
match = /traffico\/string-maps\/(\w+)-map.json/;
|
||||
|
||||
server.respondWith('GET', match, function (xhr, id) {
|
||||
xhr.respond(200, { 'Content-Type': 'application/json' },
|
||||
'{ "' + base + id + '": true }');
|
||||
});
|
||||
server.respond();
|
||||
|
||||
var sign_defs = iD.services.mapillary.sign_defs;
|
||||
|
||||
expect(sign_defs).to.have.property('au')
|
||||
.that.is.an('object')
|
||||
.that.deep.equals({'regulatory--maximum-speed-limit-65--au': true});
|
||||
expect(sign_defs).to.have.property('br')
|
||||
.that.is.an('object')
|
||||
.that.deep.equals({'regulatory--maximum-speed-limit-65--br': true});
|
||||
expect(sign_defs).to.have.property('ca')
|
||||
.that.is.an('object')
|
||||
.that.deep.equals({'regulatory--maximum-speed-limit-65--ca': true});
|
||||
expect(sign_defs).to.have.property('eu')
|
||||
.that.is.an('object')
|
||||
.that.deep.equals({'regulatory--maximum-speed-limit-65--de': true});
|
||||
expect(sign_defs).to.have.property('us')
|
||||
.that.is.an('object')
|
||||
.that.deep.equals({'regulatory--maximum-speed-limit-65--us': true});
|
||||
});
|
||||
|
||||
it('fires loadedSigns when signs are loaded', function() {
|
||||
var spy = sinon.spy();
|
||||
mapillary.on('loadedSigns', spy);
|
||||
mapillary.loadSigns(context, context.projection, dimensions);
|
||||
|
||||
var match = /search\/im\/geojson\/or/,
|
||||
rects = [{
|
||||
'package': 'trafficsign_us_3.0',
|
||||
rect: [ 0.805, 0.463, 0.833, 0.502 ],
|
||||
length: 4,
|
||||
score: '1.27',
|
||||
type: 'regulatory--maximum-speed-limit-65--us'
|
||||
}],
|
||||
features = [{
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { rects: rects, key: '0' }
|
||||
}],
|
||||
response = { type: 'FeatureCollection', features: features };
|
||||
|
||||
server.respondWith('GET', match,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
|
||||
server.respond();
|
||||
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('loads multiple pages of signs results', function() {
|
||||
var spy = sinon.spy();
|
||||
mapillary.on('loadedSigns', spy);
|
||||
mapillary.loadSigns(context, context.projection, dimensions);
|
||||
|
||||
var rects = [{
|
||||
'package': 'trafficsign_us_3.0',
|
||||
rect: [ 0.805, 0.463, 0.833, 0.502 ],
|
||||
length: 4,
|
||||
score: '1.27',
|
||||
type: 'regulatory--maximum-speed-limit-65--us'
|
||||
}],
|
||||
features0 = [],
|
||||
features1 = [],
|
||||
i;
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
features0.push({
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { rects: rects, key: String(i) }
|
||||
});
|
||||
}
|
||||
for (i = 0; i < 500; i++) {
|
||||
features1.push({
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [0,0] },
|
||||
properties: { rects: rects, key: String(1000 + i) }
|
||||
});
|
||||
}
|
||||
|
||||
var match0 = /page=0/,
|
||||
response0 = { type: 'FeatureCollection', features: features0 },
|
||||
match1 = /page=1/,
|
||||
response1 = { type: 'FeatureCollection', features: features1 }
|
||||
|
||||
server.respondWith('GET', match0,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]);
|
||||
server.respondWith('GET', match1,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]);
|
||||
server.respond();
|
||||
|
||||
expect(spy).to.have.been.calledTwice;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#images', function() {
|
||||
it('returns images in the visible map area', function() {
|
||||
var features = [
|
||||
[0, 0, 0, 0, { key: '0', loc: [0,0], ca: 90 }],
|
||||
[0, 0, 0, 0, { key: '1', loc: [0,0], ca: 90 }],
|
||||
[0, 1, 0, 1, { key: '2', loc: [0,1], ca: 90 }]
|
||||
];
|
||||
|
||||
iD.services.mapillary.cache.images.rtree.load(features);
|
||||
var res = mapillary.images(context.projection, dimensions);
|
||||
|
||||
expect(res).to.deep.eql([
|
||||
{ key: '0', loc: [0,0], ca: 90 },
|
||||
{ key: '1', loc: [0,0], ca: 90 }
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits results no more than 3 stacked images in one spot', function() {
|
||||
var features = [
|
||||
[0, 0, 0, 0, { key: '0', loc: [0,0], ca: 90 }],
|
||||
[0, 0, 0, 0, { key: '1', loc: [0,0], ca: 90 }],
|
||||
[0, 0, 0, 0, { key: '2', loc: [0,0], ca: 90 }],
|
||||
[0, 0, 0, 0, { key: '3', loc: [0,0], ca: 90 }],
|
||||
[0, 0, 0, 0, { key: '4', loc: [0,0], ca: 90 }]
|
||||
];
|
||||
|
||||
iD.services.mapillary.cache.images.rtree.load(features);
|
||||
var res = mapillary.images(context.projection, dimensions);
|
||||
expect(res).to.have.length.of.at.most(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#signs', function() {
|
||||
it('returns signs in the visible map area', function() {
|
||||
var signs = [{
|
||||
'package': 'trafficsign_us_3.0',
|
||||
rect: [ 0.805, 0.463, 0.833, 0.502 ],
|
||||
length: 4,
|
||||
score: '1.27',
|
||||
type: 'regulatory--maximum-speed-limit-65--us'
|
||||
}],
|
||||
features = [
|
||||
[0, 0, 0, 0, { key: '0', loc: [0,0], signs: signs }],
|
||||
[0, 0, 0, 0, { key: '1', loc: [0,0], signs: signs }],
|
||||
[0, 1, 0, 1, { key: '2', loc: [0,1], signs: signs }]
|
||||
];
|
||||
|
||||
iD.services.mapillary.cache.signs.rtree.load(features);
|
||||
var res = mapillary.signs(context.projection, dimensions);
|
||||
|
||||
expect(res).to.deep.eql([
|
||||
{ key: '0', loc: [0,0], signs: signs },
|
||||
{ key: '1', loc: [0,0], signs: signs }
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits results no more than 3 stacked signs in one spot', function() {
|
||||
var signs = [{
|
||||
'package': 'trafficsign_us_3.0',
|
||||
rect: [ 0.805, 0.463, 0.833, 0.502 ],
|
||||
length: 4,
|
||||
score: '1.27',
|
||||
type: 'regulatory--maximum-speed-limit-65--us'
|
||||
}],
|
||||
features = [
|
||||
[0, 0, 0, 0, { key: '0', loc: [0,0], signs: signs }],
|
||||
[0, 0, 0, 0, { key: '1', loc: [0,0], signs: signs }],
|
||||
[0, 0, 0, 0, { key: '2', loc: [0,0], signs: signs }],
|
||||
[0, 0, 0, 0, { key: '3', loc: [0,0], signs: signs }],
|
||||
[0, 0, 0, 0, { key: '4', loc: [0,0], signs: signs }]
|
||||
];
|
||||
|
||||
iD.services.mapillary.cache.signs.rtree.load(features);
|
||||
var res = mapillary.signs(context.projection, dimensions);
|
||||
expect(res).to.have.length.of.at.most(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#signsSupported', function() {
|
||||
it('returns false for Internet Explorer', function() {
|
||||
var detect = iD.detect;
|
||||
iD.detect = function() { return { ie: true, browser: 'Internet Explorer' }; };
|
||||
expect(mapillary.signsSupported()).to.be.false;
|
||||
iD.detect = detect;
|
||||
});
|
||||
|
||||
it('returns false for Safari', function() {
|
||||
var detect = iD.detect;
|
||||
iD.detect = function() { return { ie: false, browser: 'Safari' }; };
|
||||
expect(mapillary.signsSupported()).to.be.false;
|
||||
iD.detect = detect;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#signHTML', function() {
|
||||
it('returns sign HTML', function() {
|
||||
iD.services.mapillary.sign_defs = {
|
||||
us: {'regulatory--maximum-speed-limit-65--us': '<span class="t">65</span>'}
|
||||
};
|
||||
|
||||
var signdata = {
|
||||
key: '0',
|
||||
loc: [0,0],
|
||||
signs: [{
|
||||
'package': 'trafficsign_us_3.0',
|
||||
rect: [ 0.805, 0.463, 0.833, 0.502 ],
|
||||
length: 4,
|
||||
score: '1.27',
|
||||
type: 'regulatory--maximum-speed-limit-65--us'
|
||||
}]
|
||||
};
|
||||
|
||||
expect(mapillary.signHTML(signdata)).to.eql('<span class="t">65</span>')
|
||||
});
|
||||
});
|
||||
|
||||
describe('#selectedThumbnail', function() {
|
||||
it('sets thumbnail image', function() {
|
||||
mapillary.selectedThumbnail('foo');
|
||||
expect(iD.services.mapillary.thumb).to.eql('foo');
|
||||
});
|
||||
|
||||
it('gets thumbnail image', function() {
|
||||
iD.services.mapillary.thumb = 'bar';
|
||||
expect(mapillary.selectedThumbnail()).to.eql('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reset', function() {
|
||||
it('resets cache and thumbnail image', function() {
|
||||
iD.services.mapillary.cache.foo = 'bar';
|
||||
iD.services.mapillary.thumb = 'bar';
|
||||
|
||||
mapillary.reset();
|
||||
expect(iD.services.mapillary.cache).to.not.have.property('foo');
|
||||
expect(iD.services.mapillary.thumb).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
describe("iD.countryCode", function() {
|
||||
var server, countryCode;
|
||||
describe("iD.services.nominatim", function() {
|
||||
var server, nominatim;
|
||||
|
||||
beforeEach(function() {
|
||||
server = sinon.fakeServer.create();
|
||||
iD.countryCode.cache = null;
|
||||
countryCode = iD.countryCode();
|
||||
nominatim = iD.services.nominatim();
|
||||
nominatim.reset();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -15,10 +15,10 @@ describe("iD.countryCode", function() {
|
||||
return iD.util.stringQs(url.substring(url.indexOf('?') + 1));
|
||||
}
|
||||
|
||||
describe("#search", function() {
|
||||
it("calls the given callback with the results of the search query", function() {
|
||||
describe("#countryCode", function() {
|
||||
it("calls the given callback with the results of the country code query", function() {
|
||||
var callback = sinon.spy();
|
||||
countryCode.search([16, 48], callback);
|
||||
nominatim.countryCode([16, 48], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=48&lon=16",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -29,9 +29,9 @@ describe("iD.countryCode", function() {
|
||||
{format: "json", addressdetails: "1", lat: "48", lon: "16"});
|
||||
expect(callback).to.have.been.calledWith(null, "at");
|
||||
});
|
||||
it("should not cache the first search result", function() {
|
||||
it("should not cache the first country code result", function() {
|
||||
var callback = sinon.spy();
|
||||
countryCode.search([16, 48], callback);
|
||||
nominatim.countryCode([16, 48], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=48&lon=16",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -45,7 +45,7 @@ describe("iD.countryCode", function() {
|
||||
server.restore();
|
||||
server = sinon.fakeServer.create();
|
||||
|
||||
countryCode.search([17, 49], callback);
|
||||
nominatim.countryCode([17, 49], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=49&lon=17",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -56,9 +56,9 @@ describe("iD.countryCode", function() {
|
||||
{format: "json", addressdetails: "1", lat: "49", lon: "17"});
|
||||
expect(callback).to.have.been.calledWith(null, "cz");
|
||||
});
|
||||
it("should cache the first search result", function() {
|
||||
it("should cache the first country code result", function() {
|
||||
var callback = sinon.spy();
|
||||
countryCode.search([16, 48], callback);
|
||||
nominatim.countryCode([16, 48], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=48&lon=16",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -72,7 +72,7 @@ describe("iD.countryCode", function() {
|
||||
server.restore();
|
||||
server = sinon.fakeServer.create();
|
||||
|
||||
countryCode.search([16.01, 48.01], callback);
|
||||
nominatim.countryCode([16.01, 48.01], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=48.01&lon=16.01",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -83,7 +83,7 @@ describe("iD.countryCode", function() {
|
||||
});
|
||||
it("calls the given callback with an error", function() {
|
||||
var callback = sinon.spy();
|
||||
countryCode.search([1000, 1000], callback);
|
||||
nominatim.countryCode([1000, 1000], callback);
|
||||
|
||||
server.respondWith("GET", "https://nominatim.openstreetmap.org/reverse?addressdetails=1&format=json&lat=1000&lon=1000",
|
||||
[200, { "Content-Type": "application/json" },
|
||||
@@ -1,9 +1,9 @@
|
||||
describe("iD.taginfo", function() {
|
||||
describe("iD.services.taginfo", function() {
|
||||
var server, taginfo;
|
||||
|
||||
beforeEach(function() {
|
||||
server = sinon.fakeServer.create();
|
||||
taginfo = iD.taginfo();
|
||||
taginfo = iD.services.taginfo();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Reference in New Issue
Block a user