Merge pull request #4499 from openstreetmap/openstreetcam

OpenStreetCam Support!
This commit is contained in:
Bryan Housel
2017-11-07 00:09:42 -05:00
committed by GitHub
18 changed files with 1465 additions and 257 deletions
-115
View File
@@ -1,115 +0,0 @@
/* Mapillary Image Layer */
.layer-mapillary-images {
pointer-events: none;
}
.layer-mapillary-images .viewfield-group {
pointer-events: visible;
cursor: pointer; /* Opera */
cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
}
.layer-mapillary-images .viewfield-group * {
stroke-width: 1;
stroke: #444;
fill: #ffc600;
z-index: 50;
}
.layer-mapillary-images .viewfield-group:hover * {
stroke-width: 1;
stroke: #333;
fill: #ff9900;
z-index: 60;
}
.layer-mapillary-images .viewfield-group.selected * {
stroke-width: 2;
stroke: #222;
fill: #ff5800;
z-index: 60;
}
.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 .icon-sign-body {
min-width: 20px;
height: 24px;
width: 24px;
outline: 2px solid transparent;
pointer-events: visible;
cursor: pointer; /* Opera */
cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
z-index: 70;
overflow: visible;
}
.layer-mapillary-signs .icon-sign:hover .icon-sign-body {
outline: 2px solid rgba(255,198,0,0.8);
z-index: 80;
}
.layer-mapillary-signs .icon-sign.selected .icon-sign-body {
outline: 2px solid rgba(255,0,0,0.8);
z-index: 80;
}
/* Mapillary viewer */
#mly .domRenderer .TagSymbol {
font-size: 10px;
background-color: rgba(0, 0, 0, 0.4);
padding: 0 4px;
border-radius: 4px;
top: -25px;
}
#mly .domRenderer .Attribution {
width: 100%;
font-size: 10px;
text-align: right;
}
.mapillary-wrap {
position: absolute;
bottom: 30px;
width: 330px;
height: 250px;
padding: 5px;
background-color: #fff;
}
.mapillary-wrap.hidden {
visibility: hidden;
}
.mapillary-wrap button.thumb-hide {
border-radius: 0;
padding: 5px;
position: absolute;
right: 0;
top: 0;
z-index: 500;
}
.mly-wrapper {
visibility: hidden;
width: 100%;
height: 100%;
}
.mly-wrapper.active {
visibility: visible;
}
+199
View File
@@ -0,0 +1,199 @@
/* photo viewer div */
#photoviewer {
position: absolute;
bottom: 30px;
width: 330px;
height: 250px;
padding: 5px;
background-color: #fff;
}
#photoviewer button.thumb-hide {
border-radius: 0;
padding: 5px;
position: absolute;
right: 0;
top: 0;
z-index: 500;
}
.photo-wrapper,
.photo-wrapper img {
width: 100%;
height: 100%;
overflow: hidden;
}
.viewfield-group {
pointer-events: visible;
cursor: pointer;
}
.viewfield-group * {
stroke-width: 1;
stroke: #444;
z-index: 50;
}
.viewfield-group.selected * {
stroke-width: 2;
stroke: #222;
fill: #ff5800 !important;
z-index: 60;
}
.viewfield-group:hover * {
stroke-width: 1;
stroke: #333;
fill: #ff9900 !important;
z-index: 70;
}
.viewfield-group:hover path.viewfield,
.viewfield-group.selected path.viewfield,
.viewfield-group path.viewfield {
stroke-width: 0;
fill-opacity: 0.6;
}
.sequence {
stroke-width: 2;
fill: none;
}
/* Mapillary Image Layer */
.layer-mapillary-images {
pointer-events: none;
}
.layer-mapillary-images .viewfield-group * {
fill: #55ff22;
}
.layer-mapillary-images .sequence {
stroke: #55ff22;
}
/* Mapillary Sign Layer */
.layer-mapillary-signs {
pointer-events: none;
}
.layer-mapillary-signs .icon-sign .icon-sign-body {
min-width: 20px;
height: 24px;
width: 24px;
outline: 2px solid transparent;
pointer-events: visible;
cursor: pointer; /* Opera */
cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
z-index: 70;
overflow: visible;
}
.layer-mapillary-signs .icon-sign:hover .icon-sign-body {
outline: 2px solid rgba(255,198,0,0.8);
z-index: 80;
}
.layer-mapillary-signs .icon-sign.selected .icon-sign-body {
outline: 2px solid rgba(255,0,0,0.8);
z-index: 80;
}
/* OpenStreetCam Image Layer */
.layer-openstreetcam-images {
pointer-events: none;
}
.layer-openstreetcam-images .viewfield-group * {
fill: #77ddff;
}
.layer-openstreetcam-images .sequence {
stroke: #77ddff;
}
/* Mapillary viewer */
#mly .domRenderer .TagSymbol {
font-size: 10px;
background-color: rgba(0, 0, 0, 0.4);
padding: 0 4px;
border-radius: 4px;
top: -25px;
}
#mly .domRenderer .Attribution {
width: 100%;
font-size: 10px;
text-align: right;
}
/* OpenStreetCam viewer */
.osc-wrapper {
position: relative;
background-color: #000;
background-image: url(img/loader-black.gif);
background-position: center;
background-repeat: no-repeat;
}
.osc-wrapper .osc-attribution {
width: 100%;
font-size: 10px;
text-align: right;
position: absolute;
bottom: 0;
right: 0;
padding: 4px 2px;
z-index: 10;
}
.osc-attribution a,
.osc-attribution a:visited,
.osc-attribution span {
padding: 4px 2px;
color: #fff;
}
.osc-attribution a:active,
.osc-attribution a:hover {
color: #77ddff;
}
.osc-controls-wrap {
text-align: center;
position: absolute;
top: 10px;
width: 100%;
z-index: 10;
}
.osc-controls {
display: inline-block;
z-index: 10;
}
.osc-controls button {
height: 18px;
width: 18px;
background: rgba(0,0,0,0.65);
color: #eee;
border-radius: 0;
}
.osc-controls button:first-of-type {
border-radius: 3px 0 0 3px;
}
.osc-controls button:last-of-type {
border-radius: 0 3px 3px 0;
}
.osc-controls button:hover,
.osc-controls button:active,
.osc-controls button:focus {
background: rgba(0,0,0,0.85);
color: #fff;
}
+5
View File
@@ -546,6 +546,11 @@ en:
title: "Traffic Sign Overlay (Mapillary)"
mapillary:
view_on_mapillary: "View this image on Mapillary"
openstreetcam_images:
tooltip: "Street-level photos from OpenStreetCam"
title: "Photo Overlay (OpenStreetCam)"
openstreetcam:
view_on_openstreetcam: "View this image on OpenStreetCam"
help:
title: "Help"
key: H
+7
View File
@@ -670,6 +670,13 @@
"mapillary": {
"view_on_mapillary": "View this image on Mapillary"
},
"openstreetcam_images": {
"tooltip": "Street-level photos from OpenStreetCam",
"title": "Photo Overlay (OpenStreetCam)"
},
"openstreetcam": {
"view_on_openstreetcam": "View this image on OpenStreetCam"
},
"help": {
"title": "Help",
"key": "H",
+5
View File
@@ -104,6 +104,11 @@ export function rendererBackground(context) {
imageryUsed.push('Mapillary Signs');
}
var openstreetcam_images = context.layers().layer('openstreetcam-images');
if (openstreetcam_images && openstreetcam_images.enabled()) {
imageryUsed.push('OpenStreetCam Images');
}
context.history().imageryUsed(imageryUsed);
};
+4 -1
View File
@@ -1,13 +1,15 @@
import serviceMapillary from './mapillary';
import serviceNominatim from './nominatim';
import serviceOpenstreetcam from './openstreetcam';
import serviceOsm from './osm';
import serviceTaginfo from './taginfo';
import serviceWikidata from './wikidata';
import serviceWikipedia from './wikipedia';
export var services = {
mapillary: serviceMapillary,
geocoder: serviceNominatim,
mapillary: serviceMapillary,
openstreetcam: serviceOpenstreetcam,
osm: serviceOsm,
taginfo: serviceTaginfo,
wikidata: serviceWikidata,
@@ -17,6 +19,7 @@ export var services = {
export {
serviceMapillary,
serviceNominatim,
serviceOpenstreetcam,
serviceOsm,
serviceTaginfo,
serviceWikidata,
+72 -41
View File
@@ -24,7 +24,6 @@ import rbush from 'rbush';
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoExtent } from '../geo';
import { svgIcon } from '../svg';
import { utilDetect } from '../util/detect';
import { utilQsString, utilRebind } from '../util';
@@ -166,6 +165,15 @@ function loadNextTilePage(which, currZoom, url, tile) {
captured_at: feature.properties.captured_at,
pano: feature.properties.pano
};
} else if (which === 'sequences') {
var sk = feature.properties.key;
cache.lineString[sk] = feature; // cache sequence_key -> linestring
feature.properties.coordinateProperties.image_keys.forEach(function(ik) {
cache.forImage[ik] = sk; // cache image_key -> sequence_key
});
return false; // nothing to actually insert
} else if (which === 'objects') {
d = {
loc: loc,
@@ -191,11 +199,11 @@ function loadNextTilePage(which, currZoom, url, tile) {
return {
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
};
});
}).filter(Boolean);
cache.rtree.load(features);
if (which === 'images') {
if (which === 'images' || which === 'sequences') {
dispatch.call('loadedImages');
} else if (which === 'objects') {
dispatch.call('loadedSigns');
@@ -304,11 +312,15 @@ export default {
if (cache.objects && cache.objects.inflight) {
_forEach(cache.objects.inflight, abortRequest);
}
if (cache.sequences && cache.sequences.inflight) {
_forEach(cache.sequences.inflight, abortRequest);
}
}
mapillaryCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
objects: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
objects: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImage: {}, lineString: {} },
detections: {}
};
@@ -329,6 +341,29 @@ export default {
},
sequences: function(projection) {
var viewport = projection.clipExtent();
var min = [viewport[0][0], viewport[1][1]];
var max = [viewport[1][0], viewport[0][1]];
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
var sequenceKeys = {};
// all sequences for images in viewport
mapillaryCache.images.rtree.search(bbox)
.forEach(function(d) {
var sk = mapillaryCache.sequences.forImage[d.data.key];
if (sk) {
sequenceKeys[sk] = true;
}
});
// Return linestrings for the sequences
return Object.keys(sequenceKeys).map(function(sk) {
return mapillaryCache.sequences.lineString[sk];
});
},
signsSupported: function() {
var detected = utilDetect();
if (detected.ie) return false;
@@ -355,8 +390,8 @@ export default {
loadImages: function(projection) {
var url = apibase + 'images?';
loadTiles('images', url, projection);
loadTiles('images', apibase + 'images?', projection);
loadTiles('sequences', apibase + 'sequences?', projection);
},
@@ -377,28 +412,14 @@ export default {
loadViewer: function(context) {
var that = this;
var wrap = d3_select('#content').selectAll('.mapillary-wrap')
.data([0]);
var enter = wrap.enter()
.append('div')
.attr('class', 'mapillary-wrap')
.classed('al', true) // 'al'=left, 'ar'=right
.classed('hidden', true);
enter
.append('button')
.attr('class', 'thumb-hide')
.on('click', function () { that.hideViewer(); })
.append('div')
.call(svgIcon('#icon-close'));
enter
// add mly-wrapper for viewer-js
d3_select('#photoviewer').selectAll('.mly-wrapper')
.data([0])
.enter()
.append('div')
.attr('id', 'mly')
.attr('class', 'mly-wrapper')
.classed('active', false);
.attr('class', 'photo-wrapper mly-wrapper')
.classed('hide', true);
// load mapillary-viewercss
d3_select('head').selectAll('#mapillary-viewercss')
@@ -420,22 +441,32 @@ export default {
showViewer: function() {
d3_select('#content')
.selectAll('.mapillary-wrap')
.classed('hidden', false)
.selectAll('.mly-wrapper')
.classed('active', true);
var wrap = d3_select('#photoviewer')
.classed('hide', false);
var isHidden = wrap.selectAll('.photo-wrapper.mly-wrapper.hide').size();
if (isHidden) {
wrap
.selectAll('.photo-wrapper:not(.mly-wrapper)')
.classed('hide', true);
wrap
.selectAll('.photo-wrapper.mly-wrapper')
.classed('hide', false);
mapillaryViewer.resize();
}
return this;
},
hideViewer: function() {
d3_select('#content')
.selectAll('.mapillary-wrap')
.classed('hidden', true)
.selectAll('.mly-wrapper')
.classed('active', false);
d3_select('#photoviewer')
.classed('hide', true)
.selectAll('.photo-wrapper')
.classed('hide', true);
d3_selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
.classed('selected', false);
@@ -514,7 +545,7 @@ export default {
mapillaryClicks.push(imageKey);
}
d3_selectAll('.layer-mapillary-images .viewfield-group')
d3_selectAll('.viewfield-group')
.classed('selected', function(d) {
return d.key === imageKey;
});
@@ -544,12 +575,12 @@ export default {
var attribution = d3_select('.mapillary-js-dom .Attribution');
var capturedAt = attribution.selectAll('.captured-at');
if (capturedAt.empty()) {
attribution
.append('span')
.text('|');
capturedAt = attribution
.append('span')
.insert('span', ':last-child')
.attr('class', 'captured-at');
attribution
.insert('span', ':last-child')
.text('|');
}
capturedAt
.text(timestamp);
+479
View File
@@ -0,0 +1,479 @@
import _filter from 'lodash-es/filter';
import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
import { range as d3_range } from 'd3-array';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import {
select as d3_select,
selectAll as d3_selectAll
} from 'd3-selection';
import rbush from 'rbush';
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoExtent } from '../geo';
import { utilQsString, utilRebind } from '../util';
var apibase = 'http://openstreetcam.org',
maxResults = 1000,
tileZoom = 14,
dispatch = d3_dispatch('loadedImages'),
openstreetcamCache,
openstreetcamImage;
function abortRequest(i) {
i.abort();
}
function nearNullIsland(x, y, z) {
if (z >= 7) {
var center = Math.pow(2, z - 1),
width = Math.pow(2, z - 6),
min = center - (width / 2),
max = center + (width / 2) - 1;
return x >= min && x <= max && y >= min && y <= max;
}
return false;
}
function maxPageAtZoom(z) {
if (z < 15) return 2;
if (z === 15) return 5;
if (z === 16) return 10;
if (z === 17) return 20;
if (z === 18) return 40;
if (z > 18) return 80;
}
function getTiles(projection) {
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_geoTile()
.scaleExtent([tileZoom, tileZoom])
.scale(s)
.size(projection.clipExtent()[1])
.translate(projection.translate())()
.map(function(tile) {
var x = tile[0] * ts - origin[0],
y = tile[1] * ts - origin[1];
return {
id: tile.toString(),
xyz: tile,
extent: geoExtent(
projection.invert([x, y + ts]),
projection.invert([x + ts, y])
)
};
});
}
function loadTiles(which, url, projection) {
var s = projection.scale() * 2 * Math.PI,
currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
var tiles = getTiles(projection).filter(function(t) {
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
});
_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) {
loadNextTilePage(which, currZoom, url, tile);
});
}
function loadNextTilePage(which, currZoom, url, tile) {
var cache = openstreetcamCache[which];
var bbox = tile.extent.bbox();
var maxPages = maxPageAtZoom(currZoom);
var nextPage = cache.nextPage[tile.id] || 1;
var params = utilQsString({
ipp: maxResults,
page: nextPage,
// client_id: clientId,
bbTopLeft: [bbox.maxY, bbox.minX].join(','),
bbBottomRight: [bbox.minY, bbox.maxX].join(',')
}, true);
if (nextPage > maxPages) return;
var id = tile.id + ',' + String(nextPage);
if (cache.loaded[id] || cache.inflight[id]) return;
cache.inflight[id] = d3_request(url)
.mimeType('application/json')
.header('Content-type', 'application/x-www-form-urlencoded')
.response(function(xhr) { return JSON.parse(xhr.responseText); })
.post(params, function(err, data) {
cache.loaded[id] = true;
delete cache.inflight[id];
if (err || !data.currentPageItems || !data.currentPageItems.length) return;
function localeDateString(s) {
if (!s) return null;
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString();
}
var features = data.currentPageItems.map(function(item) {
var loc = [+item.lng, +item.lat],
d;
if (which === 'images') {
d = {
loc: loc,
key: item.id,
ca: +item.heading,
captured_at: localeDateString(item.shot_date || item.date_added),
captured_by: item.username,
imagePath: item.lth_name,
sequence_id: +item.sequence_id,
sequence_index: +item.sequence_index
};
// cache sequence info
var seq = openstreetcamCache.sequences[d.sequence_id];
if (!seq) {
seq = { rotation: 0, images: [] };
openstreetcamCache.sequences[d.sequence_id] = seq;
}
seq.images[d.sequence_index] = d;
}
return {
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
};
});
cache.rtree.load(features);
if (which === 'images') {
dispatch.call('loadedImages');
}
if (data.currentPageItems.length === maxResults) { // more pages to load
cache.nextPage[tile.id] = nextPage + 1;
loadNextTilePage(which, currZoom, url, tile);
} else {
cache.nextPage[tile.id] = Infinity; // no more pages to load
}
});
}
// partition viewport into `psize` x `psize` regions
function partitionViewport(psize, projection) {
var dimensions = projection.clipExtent()[1];
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(
geoExtent(projection.invert(min), projection.invert(max)));
});
});
return partitions;
}
// no more than `limit` results per partition.
function searchLimited(psize, limit, projection, rtree) {
limit = limit || 3;
var partitions = partitionViewport(psize, projection);
var results;
results = _flatten(_map(partitions, function(extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
}));
return results;
}
export default {
init: function() {
if (!openstreetcamCache) {
this.reset();
}
this.event = utilRebind(this, dispatch, 'on');
},
reset: function() {
var cache = openstreetcamCache;
if (cache) {
if (cache.images && cache.images.inflight) {
_forEach(cache.images.inflight, abortRequest);
}
}
openstreetcamCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() },
sequences: {}
};
openstreetcamImage = null;
},
images: function(projection) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, openstreetcamCache.images.rtree);
},
sequences: function(projection) {
var viewport = projection.clipExtent();
var min = [viewport[0][0], viewport[1][1]];
var max = [viewport[1][0], viewport[0][1]];
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
var seq_ids = {};
// all sequences for images in viewport
openstreetcamCache.images.rtree.search(bbox)
.forEach(function(d) { seq_ids[d.data.sequence_id] = true; });
// make linestrings from those sequences
var lineStrings = [];
Object.keys(seq_ids).forEach(function(seq_id) {
var seq = openstreetcamCache.sequences[seq_id];
var images = seq && seq.images;
if (images) {
lineStrings.push({
type: 'LineString',
coordinates: images.map(function (d) { return d.loc; }).filter(Boolean)
});
}
});
return lineStrings;
},
loadImages: function(projection) {
var url = apibase + '/1.0/list/nearby-photos/';
loadTiles('images', url, projection);
},
loadViewer: function(context) {
var that = this;
// add osc-wrapper
var wrap = d3_select('#photoviewer').selectAll('.osc-wrapper')
.data([0]);
var wrapEnter = wrap.enter()
.append('div')
.attr('class', 'photo-wrapper osc-wrapper')
.classed('hide', true);
wrapEnter
.append('div')
.attr('class', 'osc-attribution fillD');
var controlsEnter = wrapEnter
.append('div')
.attr('class', 'osc-controls-wrap')
.append('div')
.attr('class', 'osc-controls');
controlsEnter
.append('button')
.on('click.back', step(-1))
.text('◄');
controlsEnter
.append('button')
.on('click.rotate-ccw', rotate(-90))
.text('⤿');
controlsEnter
.append('button')
.on('click.rotate-cw', rotate(90))
.text('⤾');
controlsEnter
.append('button')
.on('click.forward', step(1))
.text('►');
function rotate(deg) {
return function() {
if (!openstreetcamImage) return;
var seq_id = openstreetcamImage.sequence_id;
var seq = openstreetcamCache.sequences[seq_id];
if (!seq) return;
var r = seq.rotation || 0;
r += deg;
seq.rotation = r;
d3_select('#photoviewer .osc-wrapper .osc-image')
.transition()
.duration(100)
.style('transform', 'rotate(' + r + 'deg)');
};
}
function step(stepBy) {
return function() {
if (!openstreetcamImage) return;
var seq_id = openstreetcamImage.sequence_id;
var seq = openstreetcamCache.sequences[seq_id];
if (!seq) return;
var nextIndex = openstreetcamImage.sequence_index + stepBy;
var nextImage = seq.images[nextIndex];
if (!nextImage) return;
context.map().centerEase(nextImage.loc);
that
.selectedImage(nextImage)
.updateViewer(nextImage);
};
}
},
showViewer: function() {
var viewer = d3_select('#photoviewer')
.classed('hide', false);
var isHidden = viewer.selectAll('.photo-wrapper.osc-wrapper.hide').size();
if (isHidden) {
viewer
.selectAll('.photo-wrapper:not(.osc-wrapper)')
.classed('hide', true);
viewer
.selectAll('.photo-wrapper.osc-wrapper')
.classed('hide', false);
}
return this;
},
hideViewer: function() {
d3_select('#photoviewer')
.classed('hide', true)
.selectAll('.photo-wrapper')
.classed('hide', true);
d3_selectAll('.layer-openstreetcam-images .viewfield-group')
.classed('selected', false);
openstreetcamImage = null;
return this;
},
updateViewer: function(d) {
var wrap = d3_select('#photoviewer .osc-wrapper');
wrap.selectAll('.osc-image')
.remove();
if (d) {
var seq = openstreetcamCache.sequences[d.sequence_id];
var r = (seq && seq.rotation) || 0;
wrap.append('img')
.attr('class', 'osc-image')
.style('transform', 'rotate(' + r + 'deg)')
.attr('src', apibase + '/' + d.imagePath);
var attribution = wrap.selectAll('.osc-attribution').html('');
if (d.captured_by) {
attribution
.append('a')
.attr('class', 'captured_by')
.attr('target', '_blank')
.attr('href', apibase + '/user/' + d.captured_by)
.text('@' + d.captured_by);
attribution
.append('span')
.text('|');
}
if (d.captured_at) {
attribution
.append('span')
.attr('class', 'captured_at')
.text(d.captured_at);
attribution
.append('span')
.text('|');
}
attribution
.append('a')
.attr('class', 'image_link')
.attr('target', '_blank')
.attr('href', apibase + '/details/' + d.sequence_id + '/' + d.sequence_index)
.text('openstreetcam.org');
}
return this;
},
selectedImage: function(d) {
if (!arguments.length) return openstreetcamImage;
openstreetcamImage = d;
d3_selectAll('.viewfield-group')
.classed('selected', function(d) {
return d.key === openstreetcamImage.key;
});
return this;
},
cache: function(_) {
if (!arguments.length) return openstreetcamCache;
openstreetcamCache = _;
return this;
}
};
+1
View File
@@ -10,6 +10,7 @@ export { svgMapillaryImages } from './mapillary_images.js';
export { svgMapillarySigns } from './mapillary_signs.js';
export { svgMidpoints } from './midpoints.js';
export { svgOneWaySegments } from './one_way_segments.js';
export { svgOpenstreetcamImages } from './openstreetcam_images.js';
export { svgOsm } from './osm.js';
export { svgPath } from './path.js';
export { svgPointTransform } from './point_transform.js';
+2
View File
@@ -10,6 +10,7 @@ import { svgDebug } from './debug';
import { svgGpx } from './gpx';
import { svgMapillaryImages } from './mapillary_images';
import { svgMapillarySigns } from './mapillary_signs';
import { svgOpenstreetcamImages } from './openstreetcam_images';
import { svgOsm } from './osm';
import { utilRebind } from '../util/rebind';
import { utilGetDimensions, utilSetDimensions } from '../util/dimensions';
@@ -23,6 +24,7 @@ export function svgLayers(projection, context) {
{ id: 'gpx', layer: svgGpx(projection, context, dispatch) },
{ id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) },
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },
{ id: 'openstreetcam-images', layer: svgOpenstreetcamImages(projection, context, dispatch) },
{ id: 'debug', layer: svgDebug(projection, context, dispatch) }
];
+48 -8
View File
@@ -1,5 +1,12 @@
import _throttle from 'lodash-es/throttle';
import {
geoIdentity as d3_geoIdentity,
geoPath as d3_geoPath
} from 'd3-geo';
import { select as d3_select } from 'd3-selection';
import { svgPointTransform } from './point_transform';
import { services } from '../services';
@@ -95,12 +102,35 @@ export function svgMapillaryImages(projection, context, dispatch) {
function update() {
var mapillary = getMapillary(),
data = (mapillary ? mapillary.images(projection) : []),
imageKey = mapillary ? mapillary.selectedImage() : null;
var highZoom = ~~context.map().zoom() >= minViewfieldZoom;
var mapillary = getMapillary();
var images = (mapillary ? mapillary.images(projection) : []);
var sequences = (mapillary && highZoom ? mapillary.sequences(projection) : []);
var imageKey = mapillary ? mapillary.selectedImage() : null;
var markers = layer.selectAll('.viewfield-group')
.data(data, function(d) { return d.key; });
var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
var project = projection.stream;
var makePath = d3_geoPath().projection({ stream: function(output) {
return project(clip(output));
}});
var lineStrings = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences);
lineStrings.exit()
.remove();
lineStrings = lineStrings.enter()
.append('path')
.attr('class', 'sequence')
.merge(lineStrings);
lineStrings
.attr('d', makePath);
var markers = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(images, function(d) { return d.key; });
markers.exit()
.remove();
@@ -117,7 +147,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
var viewfields = markers.selectAll('.viewfield')
.data(~~context.map().zoom() >= minViewfieldZoom ? [0] : []);
.data(highZoom ? [0] : []);
viewfields.exit()
.remove();
@@ -148,10 +178,20 @@ export function svgMapillaryImages(projection, context, dispatch) {
layer.exit()
.remove();
layer = layer.enter()
var layerEnter = layer.enter()
.append('g')
.attr('class', 'layer-mapillary-images')
.style('display', enabled ? 'block' : 'none')
.style('display', enabled ? 'block' : 'none');
layerEnter
.append('g')
.attr('class', 'sequences');
layerEnter
.append('g')
.attr('class', 'markers');
layer = layerEnter
.merge(layer);
if (enabled) {
+229
View File
@@ -0,0 +1,229 @@
import _throttle from 'lodash-es/throttle';
import { select as d3_select } from 'd3-selection';
import {
geoIdentity as d3_geoIdentity,
geoPath as d3_geoPath
} from 'd3-geo';
import { svgPointTransform } from './point_transform';
import { services } from '../services';
export function svgOpenstreetcamImages(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000),
minZoom = 12,
minViewfieldZoom = 17,
layer = d3_select(null),
_openstreetcam;
function init() {
if (svgOpenstreetcamImages.initialized) return; // run once
svgOpenstreetcamImages.enabled = false;
svgOpenstreetcamImages.initialized = true;
}
function getOpenstreetcam() {
if (services.openstreetcam && !_openstreetcam) {
_openstreetcam = services.openstreetcam;
_openstreetcam.event.on('loadedImages', throttledRedraw);
} else if (!services.openstreetcam && _openstreetcam) {
_openstreetcam = null;
}
return _openstreetcam;
}
function showLayer() {
var openstreetcam = getOpenstreetcam();
if (!openstreetcam) return;
openstreetcam.loadViewer(context);
editOn();
layer
.style('opacity', 0)
.transition()
.duration(250)
.style('opacity', 1)
.on('end', function () { dispatch.call('change'); });
}
function hideLayer() {
var openstreetcam = getOpenstreetcam();
if (openstreetcam) {
openstreetcam.hideViewer();
}
throttledRedraw.cancel();
layer
.transition()
.duration(250)
.style('opacity', 0)
.on('end', editOff);
}
function editOn() {
layer.style('display', 'block');
}
function editOff() {
layer.selectAll('.viewfield-group').remove();
layer.style('display', 'none');
}
function click(d) {
var openstreetcam = getOpenstreetcam();
if (!openstreetcam) return;
context.map().centerEase(d.loc);
openstreetcam
.selectedImage(d)
.updateViewer(d)
.showViewer();
}
function transform(d) {
var t = svgPointTransform(projection)(d);
if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
return t;
}
function update() {
var highZoom = ~~context.map().zoom() >= minViewfieldZoom;
var openstreetcam = getOpenstreetcam();
var sequences = (openstreetcam && highZoom ? openstreetcam.sequences(projection) : []);
var images = (openstreetcam ? openstreetcam.images(projection) : []);
var selectedImage = openstreetcam && openstreetcam.selectedImage();
var imageKey = selectedImage && selectedImage.key;
var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
var project = projection.stream;
var makePath = d3_geoPath().projection({ stream: function(output) {
return project(clip(output));
}});
var lineStrings = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences);
lineStrings.exit()
.remove();
lineStrings = lineStrings.enter()
.append('path')
.attr('class', 'sequence')
.merge(lineStrings);
lineStrings
.attr('d', makePath);
var markers = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(images, function(d) { return d.key; });
markers.exit()
.remove();
var enter = markers.enter()
.append('g')
.attr('class', 'viewfield-group')
.classed('selected', function(d) { return d.key === imageKey; })
.on('click', click);
markers = markers
.merge(enter)
.attr('transform', transform);
var viewfields = markers.selectAll('.viewfield')
.data(highZoom ? [0] : []);
viewfields.exit()
.remove();
viewfields.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');
markers.selectAll('circle')
.data([0])
.enter()
.append('circle')
.attr('dx', '0')
.attr('dy', '0')
.attr('r', '6');
}
function drawImages(selection) {
var enabled = svgOpenstreetcamImages.enabled,
openstreetcam = getOpenstreetcam();
layer = selection.selectAll('.layer-openstreetcam-images')
.data(openstreetcam ? [0] : []);
layer.exit()
.remove();
var layerEnter = layer.enter()
.append('g')
.attr('class', 'layer-openstreetcam-images')
.style('display', enabled ? 'block' : 'none');
layerEnter
.append('g')
.attr('class', 'sequences');
layerEnter
.append('g')
.attr('class', 'markers');
layer = layerEnter
.merge(layer);
if (enabled) {
if (openstreetcam && ~~context.map().zoom() >= minZoom) {
editOn();
update();
openstreetcam.loadImages(projection);
} else {
editOff();
}
}
}
drawImages.enabled = function(_) {
if (!arguments.length) return svgOpenstreetcamImages.enabled;
svgOpenstreetcamImages.enabled = _;
if (svgOpenstreetcamImages.enabled) {
showLayer();
} else {
hideLayer();
}
dispatch.call('change');
return this;
};
drawImages.supported = function() {
return !!getOpenstreetcam();
};
init();
return drawImages;
}
+22 -3
View File
@@ -8,9 +8,9 @@ import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import { t, textDirection } from '../util/locale';
import { tooltip } from '../util/tooltip';
import { svgDefs, svgIcon } from '../svg/index';
import { modeBrowse } from '../modes/index';
import { behaviorHash } from '../behavior/index';
import { svgDefs, svgIcon } from '../svg';
import { modeBrowse } from '../modes';
import { behaviorHash } from '../behavior';
import { utilGetDimensions } from '../util/dimensions';
import { uiAccount } from './account';
@@ -238,6 +238,25 @@ export function uiInit(context) {
.call(uiContributors(context));
var photoviewer = content
.append('div')
.attr('id', 'photoviewer')
.classed('al', true) // 'al'=left, 'ar'=right
.classed('hide', true);
photoviewer
.append('button')
.attr('class', 'thumb-hide')
.on('click', function () {
d3_select('#photoviewer')
.classed('hide', true)
.select('div')
.classed('hide', true);
})
.append('div')
.call(svgIcon('#icon-close'));
window.onbeforeunload = function() {
return context.save();
};
+39 -87
View File
@@ -80,113 +80,65 @@ export function uiMapData(context) {
}
function clickMapillaryImages() {
toggleLayer('mapillary-images');
if (!showsLayer('mapillary-images')) {
setLayer('mapillary-signs', false);
function drawPhotoItems(selection) {
var photoKeys = ['mapillary-images', 'mapillary-signs', 'openstreetcam-images'];
var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; });
var data = photoLayers.filter(function(obj) { return obj.layer.supported(); });
function layerSupported(d) {
return d.layer && d.layer.supported();
}
function layerEnabled(d) {
return layerSupported(d) && d.layer.enabled();
}
}
function clickMapillarySigns() {
toggleLayer('mapillary-signs');
}
function drawMapillaryItems(selection) {
var mapillaryImages = layers.layer('mapillary-images'),
mapillarySigns = layers.layer('mapillary-signs'),
supportsMapillaryImages = mapillaryImages && mapillaryImages.supported(),
supportsMapillarySigns = mapillarySigns && mapillarySigns.supported(),
showsMapillaryImages = supportsMapillaryImages && mapillaryImages.enabled(),
showsMapillarySigns = supportsMapillarySigns && mapillarySigns.enabled();
var mapillaryList = selection
.selectAll('.layer-list-mapillary')
var ul = selection
.selectAll('.layer-list-photos')
.data([0]);
mapillaryList = mapillaryList.enter()
ul = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-mapillary')
.merge(mapillaryList);
.attr('class', 'layer-list layer-list-photos')
.merge(ul);
var li = ul.selectAll('.list-item-photos')
.data(data);
var mapillaryImageLayerItem = mapillaryList
.selectAll('.list-item-mapillary-images')
.data(supportsMapillaryImages ? [0] : []);
mapillaryImageLayerItem.exit()
li.exit()
.remove();
var enterImages = mapillaryImageLayerItem.enter()
var liEnter = li.enter()
.append('li')
.attr('class', 'list-item-mapillary-images');
.attr('class', function(d) { return 'list-item-photos list-item-' + d.id; });
var labelImages = enterImages
var labelEnter = liEnter
.append('label')
.call(tooltip()
.title(t('mapillary_images.tooltip'))
.placement('top'));
.each(function(d) {
d3_select(this)
.call(tooltip()
.title(t(d.id.replace('-', '_') + '.tooltip'))
.placement('top')
);
});
labelImages
labelEnter
.append('input')
.attr('type', 'checkbox')
.on('change', clickMapillaryImages);
.on('change', function(d) { toggleLayer(d.id); });
labelImages
labelEnter
.append('span')
.text(t('mapillary_images.title'));
.text(function(d) { return t(d.id.replace('-', '_') + '.title'); });
var mapillarySignLayerItem = mapillaryList
.selectAll('.list-item-mapillary-signs')
.data(supportsMapillarySigns ? [0] : []);
// Update
li = li
.merge(liEnter);
mapillarySignLayerItem.exit()
.remove();
var enterSigns = mapillarySignLayerItem.enter()
.append('li')
.attr('class', 'list-item-mapillary-signs');
var labelSigns = enterSigns
.append('label')
.call(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'));
// Updates
mapillaryImageLayerItem = mapillaryImageLayerItem
.merge(enterImages);
mapillaryImageLayerItem
.classed('active', showsMapillaryImages)
li
.classed('active', layerEnabled)
.selectAll('input')
.property('checked', showsMapillaryImages);
mapillarySignLayerItem = mapillarySignLayerItem
.merge(enterSigns);
mapillarySignLayerItem
.classed('active', showsMapillarySigns)
.selectAll('input')
.property('disabled', !showsMapillaryImages)
.property('checked', showsMapillarySigns);
mapillarySignLayerItem
.selectAll('label')
.classed('deemphasize', !showsMapillaryImages);
.property('checked', layerEnabled);
}
@@ -377,7 +329,7 @@ export function uiMapData(context) {
function update() {
dataLayerContainer
.call(drawOsmItem)
.call(drawMapillaryItems)
.call(drawPhotoItems)
.call(drawGpxItem);
fillList
+1
View File
@@ -102,6 +102,7 @@
<script src='spec/services/mapillary.js'></script>
<script src='spec/services/nominatim.js'></script>
<script src='spec/services/openstreetcam.js'></script>
<script src='spec/services/osm.js'></script>
<script src='spec/services/taginfo.js'></script>
+39
View File
@@ -54,6 +54,7 @@ describe('iD.serviceMapillary', function() {
var cache = mapillary.cache();
expect(cache).to.have.property('images');
expect(cache).to.have.property('objects');
expect(cache).to.have.property('sequences');
expect(cache).to.have.property('detections');
mapillary.init();
@@ -348,6 +349,44 @@ describe('iD.serviceMapillary', function() {
});
});
describe('#sequences', function() {
it('returns sequence linestrings in the visible map area', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90 } }
];
mapillary.cache().images.rtree.load(features);
var gj = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [[10,0], [10,0], [10,1]],
properties: {
key: '-',
pano: false,
coordinateProperties: {
cas: [90, 90, 90],
image_keys: ['0', '1', '2']
}
}
}
};
mapillary.cache().sequences.lineString['-'] = gj;
mapillary.cache().sequences.forImage['0'] = '-';
mapillary.cache().sequences.forImage['1'] = '-';
mapillary.cache().sequences.forImage['2'] = '-';
var res = mapillary.sequences(context.projection);
expect(res).to.deep.eql([gj]);
});
});
describe('#signsSupported', function() {
it('returns false for Internet Explorer', function() {
ua = 'Trident/7.0; rv:11.0';
+310
View File
@@ -0,0 +1,310 @@
describe('iD.serviceOpenstreetcam', function() {
var dimensions = [64, 64],
ua = navigator.userAgent,
isPhantom = (navigator.userAgent.match(/PhantomJS/) !== null),
uaMock = function () { return ua; },
context, server, openstreetcam, orig;
before(function() {
iD.services.openstreetcam = iD.serviceOpenstreetcam;
});
after(function() {
delete iD.services.openstreetcam;
});
beforeEach(function() {
context = iD.Context().assetPath('../dist/');
context.projection
.scale(667544.214430109) // z14
.translate([-116508, 0]) // 10,0
.clipExtent([[0,0], dimensions]);
server = sinon.fakeServer.create();
openstreetcam = iD.services.openstreetcam;
openstreetcam.reset();
/* eslint-disable no-global-assign */
/* mock userAgent */
if (isPhantom) {
orig = navigator;
navigator = Object.create(orig, { userAgent: { get: uaMock }});
} else {
orig = navigator.__lookupGetter__('userAgent');
navigator.__defineGetter__('userAgent', uaMock);
}
});
afterEach(function() {
server.restore();
/* restore userAgent */
if (isPhantom) {
navigator = orig;
} else {
navigator.__defineGetter__('userAgent', orig);
}
/* eslint-enable no-global-assign */
});
describe('#init', function() {
it('Initializes cache one time', function() {
var cache = openstreetcam.cache();
expect(cache).to.have.property('images');
expect(cache).to.have.property('sequences');
openstreetcam.init();
var cache2 = openstreetcam.cache();
expect(cache).to.equal(cache2);
});
});
describe('#reset', function() {
it('resets cache and image', function() {
openstreetcam.cache({foo: 'bar'});
openstreetcam.selectedImage('baz');
openstreetcam.reset();
expect(openstreetcam.cache()).to.not.have.property('foo');
expect(openstreetcam.selectedImage()).to.be.null;
});
});
describe('#loadImages', function() {
it('fires loadedImages when images are loaded', function() {
var spy = sinon.spy();
openstreetcam.on('loadedImages', spy);
openstreetcam.loadImages(context.projection);
var data = {
status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' },
currentPageItems:[{
id: '1',
sequence_id: '100',
sequence_index: '1',
lat: '0',
lng: '10.001',
name: 'storage6\/files\/photo\/foo1.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo1.jpg',
th_name: 'storage6\/files\/photo\/th\/foo1.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}, {
id: '2',
sequence_id: '100',
sequence_index: '2',
lat: '0',
lng: '10.002',
name: 'storage6\/files\/photo\/foo2.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo2.jpg',
th_name: 'storage6\/files\/photo\/th\/foo2.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}, {
id: '3',
sequence_id: '100',
sequence_index: '3',
lat: '0',
lng: '10.003',
name: 'storage6\/files\/photo\/foo3.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo3.jpg',
th_name: 'storage6\/files\/photo\/th\/foo3.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}],
totalFilteredItems: ['3']
};
server.respondWith('POST', /nearby-photos/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(data) ]);
server.respond();
expect(spy).to.have.been.calledOnce;
});
it('does not load images around null island', function() {
var spy = sinon.spy();
context.projection.translate([0,0]);
openstreetcam.on('loadedImages', spy);
openstreetcam.loadImages(context.projection);
var data = {
status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' },
currentPageItems:[{
id: '1',
sequence_id: '100',
sequence_index: '1',
lat: '0',
lng: '0',
name: 'storage6\/files\/photo\/foo1.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo1.jpg',
th_name: 'storage6\/files\/photo\/th\/foo1.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}, {
id: '2',
sequence_id: '100',
sequence_index: '2',
lat: '0',
lng: '0',
name: 'storage6\/files\/photo\/foo2.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo2.jpg',
th_name: 'storage6\/files\/photo\/th\/foo2.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}, {
id: '3',
sequence_id: '100',
sequence_index: '3',
lat: '0',
lng: '0',
name: 'storage6\/files\/photo\/foo3.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo3.jpg',
th_name: 'storage6\/files\/photo\/th\/foo3.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
}],
totalFilteredItems: ['3']
};
server.respondWith('POST', /nearby-photos/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(data) ]);
server.respond();
expect(spy).to.have.been.not.called;
});
it.skip('loads multiple pages of image results', function() {
var spy = sinon.spy();
openstreetcam.on('loadedImages', spy);
openstreetcam.loadImages(context.projection);
var features0 = [],
features1 = [],
i;
for (i = 0; i < 1000; i++) {
features0.push({
id: String(i),
sequence_id: '100',
sequence_index: String(i),
lat: '10',
lng: '0',
name: 'storage6\/files\/photo\/foo' + String(i) +'.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo' + String(i) +'.jpg',
th_name: 'storage6\/files\/photo\/th\/foo' + String(i) +'.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
});
}
for (i = 0; i < 500; i++) {
features1.push({
id: String(i),
sequence_id: '100',
sequence_index: String(1000 + i),
lat: '10',
lng: '0',
name: 'storage6\/files\/photo\/foo' + String(1000 + i) +'.jpg',
lth_name: 'storage6\/files\/photo\/lth\/foo' + String(1000 + i) +'.jpg',
th_name: 'storage6\/files\/photo\/th\/foo' + String(1000 + i) +'.jpg',
shot_date: '2017-09-24 23:58:07',
heading: '90',
username: 'test'
});
}
var response0 = {
status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' },
currentPageItems: [features0],
totalFilteredItems: ['1000']
},
response1 = {
status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' },
currentPageItems: [features1],
totalFilteredItems: ['500']
};
server.respondWith('POST', /nearby-photos/, function (request) {
var response;
if (request.requestBody.match(/page=1/) !== null) {
response = JSON.stringify(response0);
} else if (request.requestBody.match(/page=2/) !== null) {
response = JSON.stringify(response1);
}
request.respond(200, {'Content-Type': 'application/json'}, response);
});
server.respond();
expect(spy).to.have.been.calledTwice;
});
});
describe('#images', function() {
it('returns images in the visible map area', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: 100, sequence_index: 2 } }
];
openstreetcam.cache().images.rtree.load(features);
var res = openstreetcam.images(context.projection);
expect(res).to.deep.eql([
{ key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 },
{ key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 }
]);
});
it('limits results no more than 3 stacked images in one spot', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 2 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 3 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 4 } }
];
openstreetcam.cache().images.rtree.load(features);
var res = openstreetcam.images(context.projection);
expect(res).to.have.length.of.at.most(3);
});
});
describe('#sequences', function() {
it('returns sequence linestrings in the visible map area', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: 100, sequence_index: 2 } }
];
openstreetcam.cache().images.rtree.load(features);
openstreetcam.cache().sequences['100'] = { rotation: 0, images: [ features[0].data, features[1].data, features[2].data ] };
var res = openstreetcam.sequences(context.projection);
expect(res).to.deep.eql([{
type: 'LineString',
coordinates: [[10,0], [10,0], [10,1]]
}]);
});
});
describe('#selectedImage', function() {
it('sets and gets selected image', function() {
openstreetcam.selectedImage('foo');
expect(openstreetcam.selectedImage()).to.eql('foo');
});
});
});
+3 -2
View File
@@ -26,12 +26,13 @@ describe('iD.svgLayers', function () {
it('creates default data layers', function () {
container.call(iD.svgLayers(projection, context));
var nodes = container.selectAll('svg .data-layer').nodes();
expect(nodes.length).to.eql(5);
expect(nodes.length).to.eql(6);
expect(d3.select(nodes[0]).classed('data-layer-osm')).to.be.true;
expect(d3.select(nodes[1]).classed('data-layer-gpx')).to.be.true;
expect(d3.select(nodes[2]).classed('data-layer-mapillary-images')).to.be.true;
expect(d3.select(nodes[3]).classed('data-layer-mapillary-signs')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-debug')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-openstreetcam-images')).to.be.true;
expect(d3.select(nodes[5]).classed('data-layer-debug')).to.be.true;
});
});