diff --git a/modules/services/index.js b/modules/services/index.js
index d5ab2d365..aa5055b5c 100644
--- a/modules/services/index.js
+++ b/modules/services/index.js
@@ -19,6 +19,7 @@ export var services = {
export {
serviceMapillary,
serviceNominatim,
+ serviceOpenstreetcam,
serviceOsm,
serviceTaginfo,
serviceWikidata,
diff --git a/test/index.html b/test/index.html
index c391ca88e..18092851b 100644
--- a/test/index.html
+++ b/test/index.html
@@ -102,6 +102,7 @@
+
diff --git a/test/spec/services/mapillary.js b/test/spec/services/mapillary.js
index dd184db05..e70d896f1 100644
--- a/test/spec/services/mapillary.js
+++ b/test/spec/services/mapillary.js
@@ -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';
diff --git a/test/spec/services/openstreetcam.js b/test/spec/services/openstreetcam.js
new file mode 100644
index 000000000..facdd55ac
--- /dev/null
+++ b/test/spec/services/openstreetcam.js
@@ -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');
+ });
+ });
+
+});