Merge pull request #2720 from openstreetmap/mapillary-streetsigns2

Mapillary street signs
This commit is contained in:
Bryan Housel
2016-02-16 16:52:30 -05:00
33 changed files with 1580 additions and 466 deletions
+3 -3
View File
@@ -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);
```
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+8 -2
View File
@@ -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": {
Vendored Submodule
+1
Submodule dist/traffico added at 2ff4ac82f1
+7 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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() {
+54 -11
View File
@@ -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();
};
+4
View File
@@ -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')
+182
View File
@@ -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;
};
-159
View File
@@ -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;
};
+164
View File
@@ -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;
};
+1
View File
@@ -0,0 +1 @@
iD.services = {};
+274
View File
@@ -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;
};
+13 -6
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
iD.wikipedia = function() {
iD.services.wikipedia = function() {
var wiki = {},
endpoint = 'https://en.wikipedia.org/w/api.php?';
+174 -92
View File
@@ -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()
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+4 -3
View File
@@ -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
View File
@@ -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]]);
});
});
+336
View File
@@ -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() {