commit 51a8993c826ae8bd0cec2ae78602b6e17828a836
parent 9c057604c8b760645e811acb5aaedcea5954d2f6
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Fri, 7 Jul 2017 01:07:32 +0300
[panoramas] refacored, added mapillary
Diffstat:
16 files changed, 999 insertions(+), 459 deletions(-)
diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js
@@ -96,7 +96,7 @@ module.exports = {
test: /\.(js|jsx)$/,
loader: 'eslint',
include: paths.appSrc,
- exclude: /augustl\/js-unzip|dankogai\/js-deflate/,
+ exclude: /augustl\/js-unzip|dankogai\/js-deflate|mapillary-js/,
}
],
loaders: [
@@ -112,7 +112,7 @@ module.exports = {
// Process JS with Babel.
{
test: /\.(js|jsx)$/,
- exclude: /augustl\/js-unzip|dankogai\/js-deflate/,
+ exclude: /augustl\/js-unzip|dankogai\/js-deflate|mapillary-js/,
include: paths.appSrc,
loader: 'babel',
},
diff --git a/package.json b/package.json
@@ -61,6 +61,7 @@
"knockout": "^3.4.0",
"leaflet": "1.0.3",
"load-script": "^1.0.0",
+ "mapillary-js": "2.5.2",
"pbf": "^3.0.5",
"raven-js": "^3.12.0",
"rbush": "^2.0.1",
diff --git a/src/App.js b/src/App.js
@@ -27,6 +27,7 @@ import 'lib/leaflet.control.jnx';
import 'lib/leaflet.control.jnx/hash-state';
import 'lib/leaflet.control.azimuth';
+
function setUp() {
fixAll();
@@ -58,7 +59,7 @@ function setUp() {
new L.Control.Panoramas(document.getElementById('street-view'))
.addTo(map)
- .enableHashState('n');
+ .enableHashState('n2');
new L.Control.Coordinates({position: 'topleft'}).addTo(map);
diff --git a/src/lib/controls-styles/controls-styles.css b/src/lib/controls-styles/controls-styles.css
@@ -1,4 +1,4 @@
-.leaflet-control.control-form {
+.leaflet-control.control-form, .leaflet-control .control-form{
background-color: white;
border-radius: 5px 5px 5px 5px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
diff --git a/src/lib/leaflet.control.panoramas/index.js b/src/lib/leaflet.control.panoramas/index.js
@@ -1,9 +1,10 @@
import L from 'leaflet';
import './style.css';
import 'lib/controls-styles/controls-styles.css';
-import getGoogle from 'lib/googleMapsApi';
-import 'lib/leaflet.hashState/leaflet.hashState';
-import 'lib/leaflet.control.commons';
+import ko from 'vendored/knockout';
+import googleProvider from './lib/google';
+import mapillaryProvider from './lib/mapillary';
+
function fireRefreshEventOnWindow() {
const evt = document.createEvent("HTMLEvents");
@@ -11,6 +12,24 @@ function fireRefreshEventOnWindow() {
window.dispatchEvent(evt);
}
+
+const PanoMarker = L.Marker.extend({
+ initialize: function() {
+ const icon = L.divIcon({
+ className: 'leaflet-panorama-marker-wraper',
+ html: '<div class="leaflet-panorama-marker"></div>'
+ }
+ );
+ L.Marker.prototype.initialize.call(this, [0, 0], {icon, interactive: false});
+ },
+
+ setHeading: function(angle) {
+ let markerIcon = this.getElement();
+ markerIcon = markerIcon.children[0];
+ markerIcon.style.transform = `rotate(${angle}deg)`;
+ }
+});
+
L.Control.Panoramas = L.Control.extend({
includes: L.Mixin.Events,
@@ -20,188 +39,235 @@ L.Control.Panoramas = L.Control.extend({
initialize: function(panoramaContainer, options) {
L.Control.prototype.initialize.call(this, options);
+ this.googleCoverageSelected = ko.observable(true);
+ this.mapillaryCoverageSelected = ko.observable(false);
+ this.googleCoverageSelected.subscribe(this.updateCoverageVisibility, this);
+ this.mapillaryCoverageSelected.subscribe(this.updateCoverageVisibility, this);
this._panoramaContainer = panoramaContainer;
-
- const icon = L.divIcon({
- className: 'leaflet-panorama-marker-wraper',
- html: '<div class="leaflet-panorama-marker"></div>'
- }
- );
- this.marker = L.marker([0, 0], {icon: icon, interactive: false});
+ this._googlePanoramaContainer = L.DomUtil.create('div', 'panorama-container', panoramaContainer);
+ this._mapillaryPanoramaContainer = L.DomUtil.create('div', 'panorama-container', panoramaContainer);
},
onAdd: function(map) {
this._map = map;
- const container = this._container = L.DomUtil.create('a', 'leaflet-control leaflet-control-button leaflet-contol-panoramas');
+ const container = this._container = L.DomUtil.create('div', 'leaflet-control leaflet-contol-panoramas');
+ container.innerHTML = `
+ <a name="button" class="panoramas-button leaflet-control-button icon-panoramas" title="Show panoramas"
+ data-bind="click: onButtonClick"></a>
+ <div class="panoramas-list control-form">
+ <div><label><input type="checkbox" data-bind="checked: googleCoverageSelected">Google street view</label></div>
+ <div><label><input type="checkbox" data-bind="checked: mapillaryCoverageSelected">Mapillary</label></div>
+ </div>
+ `;
this._stopContainerEvents();
- container.title = 'Show panoramas';
- L.DomEvent.on(container, 'click', this.onButtonClick, this);
-
+ ko.applyBindings(this, container);
map.createPane('rasterOverlay').style.zIndex = 300;
- this._coverageLayer = L.tileLayer(
- 'https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m8!1e2!2ssvv!4m2!1scb_client!2sapiv3!4m2!1scc!2s*211m3*211e3*212b1*213e2*211m3*211e2*212b1*213e2!3m5!3sUS!12m1!1e40!12m1!1e18!4e0',
- {pane: 'rasterOverlay'}
- );
-
return container;
},
- onRemove: function() {
- this._map = null;
- this.hideCoverage();
- this.hidePanorama();
+ onButtonClick: function() {
+ if (this.controlEnabled) {
+ this.disableControl();
+ } else {
+ this.enableControl();
+ }
},
- showPanorama: function() {
- if (this.panoramaVisible) {
+ enableControl: function() {
+ if (this.controlEnabled) {
return;
}
- L.DomUtil.addClass(this._panoramaContainer, 'enabled');
- this.getGoogleApi().then((api) => api.panorama.setVisible(true));
- fireRefreshEventOnWindow();
- this.marker.addTo(this._map);
- this.panoramaVisible = true;
+ this.controlEnabled = true;
+ L.DomUtil.addClass(this._container, 'enabled');
+ this.updateCoverageVisibility();
+ this._map.on('click', this.onMapClick, this);
+ L.DomUtil.addClass(this._map._container, 'panoramas-control-active');
this.notifyChanged();
},
- hidePanorama: function() {
- if (!this.panoramaVisible) {
+ disableControl: function() {
+ if (!this.controlEnabled) {
return;
}
- this.getGoogleApi().then((api) => api.panorama.setVisible(false));
- L.DomUtil.removeClass(this._panoramaContainer, 'enabled');
- fireRefreshEventOnWindow();
- this._map.removeLayer(this.marker);
- this.panoramaVisible = false;
+ this.controlEnabled = false;
+ L.DomUtil.removeClass(this._container, 'enabled');
+ this.updateCoverageVisibility();
+ this._map.off('click', this.onMapClick, this);
+ this.hidePanoViewer();
+ L.DomUtil.removeClass(this._map._container, 'panoramas-control-active');
this.notifyChanged();
},
- showCoverage: function() {
- if (this.coverageVisible) {
+ updateCoverageVisibility: function() {
+ if (!this._map) {
return;
}
- L.DomUtil.addClass(this.getContainer(), 'enabled');
- L.DomUtil.addClass(this._map._container, 'panoramas-control-active');
- this._coverageLayer.addTo(this._map);
- this._map.on('click', this.onMapClick, this);
- this.coverageVisible = true;
+ if (this.controlEnabled && this.googleCoverageSelected()) {
+ if (!this.googleCoverage) {
+ this.googleCoverage = googleProvider.getCoverageLayer({pane: 'rasterOverlay', zIndex: 2});
+ }
+ this.googleCoverage.addTo(this._map);
+ } else {
+ if (this.googleCoverage) {
+ this.googleCoverage.removeFrom(this._map)
+ }
+ }
+
+ if (this.controlEnabled && this.mapillaryCoverageSelected()) {
+ if (!this.mapillaryCoverage) {
+ this.mapillaryCoverage = mapillaryProvider.getCoverageLayer({pane: 'rasterOverlay', opacity: 0.7,
+ zIndex: 1});
+ }
+ this.mapillaryCoverage.addTo(this._map);
+ } else {
+ if (this.mapillaryCoverage) {
+ this.mapillaryCoverage.removeFrom(this._map)
+ }
+ }
this.notifyChanged();
},
- onMapClick: function(e) {
- this.showPanoramaAtPos(e.latlng);
+ showPanoramaContainer: function() {
+ L.DomUtil.addClass(this._panoramaContainer, 'enabled');
+ fireRefreshEventOnWindow();
},
- showPanoramaAtPos: function(latlng, pov) {
- this.showPanorama();
- const searchRadiusPx = 24;
- const p = this._map.project(latlng).add([searchRadiusPx, 0]);
- const searchRadiusMeters = latlng.distanceTo(this._map.unproject(p));
-
- function setPanoramaPosition(api, panoData, status) {
- if (status === api.google.maps.StreetViewStatus.OK) {
- api.panorama.setPosition(panoData.location.latLng);
+ panoramaVisible: function() {
+ if (L.DomUtil.hasClass(this._panoramaContainer, 'enabled')) {
+ if (L.DomUtil.hasClass(this._googlePanoramaContainer, 'enabled')) {
+ return 'google';
}
- if (pov) {
- api.panorama.setPov(pov);
+ if (L.DomUtil.hasClass(this._mapillaryPanoramaContainer, 'enabled')) {
+ return 'mapillary';
}
}
+ return false;
+ },
- this.getGoogleApi().then((api) => {
- api.service.getPanorama({
- location: latlng,
- radius: searchRadiusMeters,
- preference: api.google.maps.StreetViewPreference.NEAREST
- }, setPanoramaPosition.bind(null, api)
- );
- }
- );
+ hidePanoGoogle: function() {
+ L.DomUtil.removeClass(this._googlePanoramaContainer, 'enabled');
+ if (this.googleViewer) {
+ this.googleViewer.deactivate();
+ }
},
- hideCoverage: function() {
- if (!this.coverageVisible) {
- return;
+ hidePanoMapillary: function() {
+ L.DomUtil.removeClass(this._mapillaryPanoramaContainer, 'enabled');
+ if (this.mapillaryViewer) {
+ this.mapillaryViewer.deactivate();
+ }
+ },
+
+ showPanoGoogle: async function(data) {
+ this.hidePanoMapillary();
+ this.showPanoramaContainer();
+ L.DomUtil.addClass(this._googlePanoramaContainer, 'enabled');
+ if (!this.googleViewer) {
+ this.googleViewer = await googleProvider.getViewer(this._googlePanoramaContainer);
+ this.setupViewerEvents(this.googleViewer);
+ }
+ this.googleViewer.activate();
+ if (data) {
+ this.googleViewer.showPano(data);
}
- L.DomUtil.removeClass(this.getContainer(), 'enabled');
- L.DomUtil.removeClass(this._map._container, 'panoramas-control-active');
- this._coverageLayer.removeFrom(this._map);
- this._map.off('click', this.onMapClick, this);
- this.coverageVisible = false;
this.notifyChanged();
},
- onButtonClick: function() {
- if (!this.coverageVisible) {
- this.showCoverage();
- } else {
- this.hideCoverage();
- this.hidePanorama();
+ showPanoMapillary: async function(data) {
+ this.showPanoramaContainer();
+ this.hidePanoGoogle();
+ L.DomUtil.addClass(this._mapillaryPanoramaContainer, 'enabled');
+ if (!this.mapillaryViewer) {
+ this.mapillaryViewer = await mapillaryProvider.getViewer(this._mapillaryPanoramaContainer);
+ this.setupViewerEvents(this.mapillaryViewer);
}
+ if (data) {
+ this.mapillaryViewer.showPano(data);
+ }
+ this.mapillaryViewer.activate();
+ this.notifyChanged();
},
- onPanoramaChangePosition: function() {
- this.getGoogleApi().then((api) => {
- let pos = api.panorama.getPosition();
- if (pos) {
- pos = L.latLng([pos.lat(), pos.lng()]);
- this.marker.setLatLng(pos);
- if (!this._map.getBounds().contains(pos)) {
- this._map.panTo(pos);
- }
- this.panoramaPosition = pos;
- } else {
- this.panoramaPosition = null;
- }
- this.notifyChanged();
- }
- );
+ setupViewerEvents: function(viewer) {
+ viewer.on({
+ 'change': this.onPanoramaChangeView,
+ 'closeclick': this.onPanoramaCloseClick
+ }, this);
},
- onPanoramaChangeView: function() {
- let markerIcon = this.marker.getElement();
- if (markerIcon) {
- markerIcon = markerIcon.children[0]
+ hidePanoViewer: function() {
+ this.hidePanoGoogle();
+ this.hidePanoMapillary();
+ L.DomUtil.removeClass(this._panoramaContainer, 'enabled');
+ this.hideMarker();
+ fireRefreshEventOnWindow();
+ this.notifyChanged();
+ },
+
+
+ placeMarker: function(latlng, heading) {
+ if (!this.panoramaVisible()) {
+ return;
}
- this.getGoogleApi().then((api) => {
- const pov = api.panorama.getPov();
- if (markerIcon) {
- markerIcon.style.transform = `rotate(${pov.heading}deg)`;
- }
- this.panoramaAngle = pov;
- this.notifyChanged();
- }
- );
+ if (!this.marker) {
+ this.marker = new PanoMarker();
+ }
+ this._map.addLayer(this.marker);
+ this.marker.setLatLng(latlng);
+ this.marker.setHeading(heading);
},
+ hideMarker: function() {
+ if (this.marker) {
+ this._map.removeLayer(this.marker);
+ }
+ },
notifyChanged: function() {
this.fire('panoramachanged');
},
- getGoogleApi: function() {
- if (!this._googleApi) {
- this._googleApi = getGoogle().then((google) => {
- const panorama = new google.maps.StreetViewPanorama(this._panoramaContainer, {
- enableCloseButton: true,
- imageDateControl: true
- }
- );
- panorama.addListener('position_changed', this.onPanoramaChangePosition.bind(this));
- panorama.addListener('pov_changed', this.onPanoramaChangeView.bind(this));
- panorama.addListener('closeclick', this.hidePanorama.bind(this));
+ onPanoramaChangeView: function(e) {
+ if (!this._map.getBounds().pad(-0.05).contains(e.latlng)) {
+ this._map.panTo(e.latlng);
+ }
+ this.placeMarker(e.latlng, e.heading);
+ this.notifyChanged();
+ },
- return {
- google,
- service: new google.maps.StreetViewService(),
- panorama
+ onPanoramaCloseClick: function(e) {
+ this.hidePanoViewer();
+ },
- }
- }
- );
+ onMapClick: async function(e) {
+ let
+ googlePanoPromise, mapillaryPanoPromise;
+ const
+ searchRadiusPx = 24,
+ p = this._map.project(e.latlng).add([searchRadiusPx, 0]),
+ searchRadiusMeters = e.latlng.distanceTo(this._map.unproject(p));
+ if (this.googleCoverageSelected()) {
+ googlePanoPromise = googleProvider.getPanoramaAtPos(e.latlng, searchRadiusMeters);
+ }
+ if (this.mapillaryCoverageSelected()) {
+ mapillaryPanoPromise = mapillaryProvider.getPanoramaAtPos(e.latlng, searchRadiusMeters);
+ }
+ if (googlePanoPromise) {
+ let searchResult = await googlePanoPromise;
+ if (searchResult.found) {
+ this.showPanoGoogle(searchResult.data);
+ return;
+ }
}
- return this._googleApi;
+ if (mapillaryPanoPromise) {
+ let searchResult = await mapillaryPanoPromise;
+ if (searchResult.found) {
+ this.showPanoMapillary(searchResult.data);
+ return;
+ }
+ }
}
- }
+ },
);
L.Control.Panoramas.include(L.Mixin.HashState);
@@ -209,17 +275,29 @@ L.Control.Panoramas.include({
stateChangeEvents: ['panoramachanged'],
serializeState: function() {
- if (!this.coverageVisible) {
- return null;
- }
- const state = [];
- if (this.panoramaVisible && this.panoramaPosition && this.panoramaAngle !== undefined) {
- state.push(this.panoramaPosition.lat.toFixed(5));
- state.push(this.panoramaPosition.lng.toFixed(5));
- state.push(this.panoramaAngle.heading.toFixed(1));
- state.push(this.panoramaAngle.pitch.toFixed(1));
- // in safari zoom is undefined
- state.push((this.panoramaAngle.zoom || 0).toFixed(1));
+ let state = null;
+ if (this.controlEnabled) {
+ state = [];
+ let coverageCode='_';
+ if (this.mapillaryCoverageSelected()) {
+ coverageCode += 'm';
+ }
+ if (this.googleCoverageSelected()) {
+ coverageCode += 'g';
+ }
+ state.push(coverageCode);
+ const panoramaVisible = this.panoramaVisible();
+ if (panoramaVisible) {
+ let code = {'google': 'g', 'mapillary': 'm'}[panoramaVisible];
+ let viewer = {'google': this.googleViewer, 'mapillary': this.mapillaryViewer}[panoramaVisible];
+ if (viewer) {
+ let viewerState = viewer.getState();
+ if (viewerState) {
+ state.push(code);
+ state.push(...viewerState);
+ }
+ }
+ }
}
return state;
},
@@ -227,28 +305,23 @@ L.Control.Panoramas.include({
unserializeState: function(state) {
if (!state) {
- this.hidePanorama();
- this.hideCoverage();
+ this.disableControl();
return true;
}
- if (state.length === 0) {
- this.hidePanorama();
- this.showCoverage();
- return true;
- }
-
- const lat = parseFloat(state[0]);
- const lng = parseFloat(state[1]);
- const heading = parseFloat(state[2]);
- const pitch = parseFloat(state[3]);
- const zoom = parseFloat(state[4]);
- if (!isNaN(lat) && !isNaN(lng) && !isNaN(heading) && !isNaN(pitch) && !isNaN(zoom)) {
- this.showCoverage();
- this.showPanoramaAtPos(L.latLng(lat, lng), {heading, pitch, zoom});
- return true;
+ this.enableControl();
+ const coverageCode = state[0];
+ this.googleCoverageSelected(coverageCode.includes('g'));
+ this.mapillaryCoverageSelected(coverageCode.includes('m'));
+ if (state.length > 2) {
+ const panoramaVisible = state[1];
+ if (panoramaVisible === 'g') {
+ this.showPanoGoogle().then(() => this.googleViewer.setState(state.slice(2)));
+ }
+ if (panoramaVisible === 'm') {
+ this.showPanoMapillary().then(() => this.mapillaryViewer.setState(state.slice(2)));
+ }
}
-
- return false;
+ return true;
}
}
);
\ No newline at end of file
diff --git a/src/lib/leaflet.control.panoramas/lib/google/index.js b/src/lib/leaflet.control.panoramas/lib/google/index.js
@@ -0,0 +1,112 @@
+import L from 'leaflet';
+import getGoogle from 'lib/googleMapsApi';
+
+
+function getCoverageLayer(options) {
+ return L.tileLayer(
+ 'https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m8!1e2!2ssvv!4m2!1scb_client!2sapiv3!4m2!1scc!2s*211m3*211e3*212b1*213e2*211m3*211e2*212b1*213e2!3m5!3sUS!12m1!1e40!12m1!1e18!4e0',
+ options
+ );
+}
+
+
+async function getStreetViewService() {
+ const google = await getGoogle();
+ return new google.maps.StreetViewService();
+}
+
+
+async function getPanoramaAtPos(latlng, searchRadiusMeters) {
+ const google = await getGoogle();
+ const service = await getStreetViewService();
+ const {data, status} = await new Promise((resolve) => {
+ service.getPanorama({
+ location: latlng,
+ radius: searchRadiusMeters,
+ preference: google.maps.StreetViewPreference.NEAREST
+ }, (data, status) => resolve({data, status})
+ )
+ }
+ );
+ if (status === google.maps.StreetViewStatus.OK) {
+ return {found: true, data};
+ } else {
+ return {found: false};
+ }
+}
+
+
+const Viewer = L.Evented.extend({
+ initialize: function(google, container) {
+ const panorama = this.panorama = new google.maps.StreetViewPanorama(container, {
+ enableCloseButton: true,
+ imageDateControl: true
+ }
+ );
+ panorama.addListener('position_changed', () => this.onPanoramaChangeView());
+ panorama.addListener('pov_changed', () => this.onPanoramaChangeView());
+ panorama.addListener('closeclick', () => this.onCloseClick());
+ },
+
+ showPano: function(data) {
+ this.panorama.setPosition(data.location.latLng);
+ },
+
+ onPanoramaChangeView: function() {
+ if (!this._active) {
+ return;
+ }
+ let pos = this.panorama.getPosition();
+ pos = L.latLng(pos.lat(), pos.lng());
+ const pov = this.panorama.getPov();
+ this.fire('change', {latlng: pos, heading: pov.heading, zoom: pov.zoom, pitch: pov.pitch});
+ },
+
+ onCloseClick: function() {
+ this.fire('closeclick');
+ },
+
+ activate: function() {
+ this._active = true;
+ this.panorama.setVisible(true);
+ },
+
+ deactivate: function() {
+ this._active = false;
+ this.panorama.setVisible(false);
+ },
+
+ getState: function() {
+ const pos = this.panorama.getPosition();
+ const pov = this.panorama.getPov();
+ if (pos && pov) {
+ return [pos.lat().toFixed(6), pos.lng().toFixed(6),
+ (pov.heading || 0).toFixed(1), (pov.pitch || 0).toFixed(1), (pov.zoom || 1).toFixed(1)];
+ } else {
+ return null;
+ }
+ },
+
+ setState: function(state) {
+ const lat = parseFloat(state[0]);
+ const lng = parseFloat(state[1]);
+ const heading = parseFloat(state[2]);
+ const pitch = parseFloat(state[3]);
+ const zoom = parseFloat(state[4]);
+ if (!isNaN(lat) && !isNaN(lng) && !isNaN(heading) && !isNaN(pitch) && !isNaN(zoom)) {
+ this.panorama.setPosition({lat, lng});
+ this.panorama.setPov({heading, pitch, zoom});
+ return true;
+ }
+ return false;
+
+ }
+});
+
+async function getViewer(container) {
+ const google = await getGoogle();
+ return new Viewer(google, container)
+}
+
+
+export default {getCoverageLayer, getPanoramaAtPos, getViewer};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.panoramas/lib/mapillary/index.js b/src/lib/leaflet.control.panoramas/lib/mapillary/index.js
@@ -0,0 +1,169 @@
+import L from 'leaflet';
+import {MapillaryCoverage} from './mapillary-coverage-layer'
+import {fetch} from 'lib/xhr-promise';
+import config from 'config';
+
+
+function getCoverageLayer(options) {
+ return new MapillaryCoverage(options);
+}
+
+function getMapillary() {
+ return new Promise((resolve) => {
+ require.ensure(['mapillary-js/dist/mapillary.js', 'mapillary-js/dist/mapillary.min.css'], () => {
+ require('mapillary-js/dist/mapillary.min.css');
+ resolve(require('mapillary-js/dist/mapillary.js'));
+ }, 'mapillary');
+ });
+}
+
+
+async function getPanoramaAtPos(latlng, searchRadiusMeters) {
+ const url = `https://a.mapillary.com/v3/images?client_id=${config.mapillary}&closeto=${latlng.lng},${latlng.lat}&radius=${searchRadiusMeters}`;
+ const resp = await fetch(url, {responseType: 'json', timeout: 10000});
+ if (resp.status === 200 && resp.response.features.length) {
+ return {found: true, data: resp.response.features[0].properties.key};
+ }
+ return {found: false};
+}
+
+function formatDateTime(ts) {
+ const d = new Date(ts);
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}`;
+}
+
+
+const Viewer = L.Evented.extend({
+ initialize: function(mapillary, container) {
+ const id = `container-${L.stamp(container)}`;
+ container.id = id;
+ const viewer = this.viewer = new mapillary.Viewer(
+ id, config.mapillary, null, {component: {cover: false, bearing: false}});
+ window.addEventListener('resize', function() {
+ viewer.resize();
+ }
+ );
+ viewer.on('nodechanged', this.onNodeChanged.bind(this));
+ viewer.on('bearingchanged', this.onBearingChanged.bind(this));
+ this.dateLabel = L.DomUtil.create('div', 'mapillary-viewer-date-overlay', container);
+ this.closeButton = L.DomUtil.create('div', 'mapillary-viewer-button-close', container);
+ L.DomEvent.on(this.closeButton, 'click', this.onCloseClick, this);
+ this._bearing = 0;
+ this._zoom = 0;
+ this._center = [0, 0];
+ },
+
+ showPano: function(data) {
+ this.deactivate();
+ this.activate();
+ this.viewer.moveToKey(data).then(() => {
+ this.viewer.setZoom(0);
+ this.updateZoomAndCenter();
+ });
+ },
+
+ onNodeChanged: function(node) {
+ if (this._node && (node.key === this._node.key)) {
+ return;
+ }
+ this._node = node;
+ this.fireChangeEvenet();
+ this.dateLabel.innerHTML = formatDateTime(node.capturedAt);
+ },
+
+ getBearingCorrection: function() {
+ if (this._node && 'computedCA' in this._node) {
+ return (this._node.computedCA - this._node.originalCA);
+ }
+ return 0;
+ },
+
+ onBearingChanged: function(bearing) {
+ bearing -= this.getBearingCorrection();
+ if (this._bearing === bearing) {
+ return;
+ }
+ this._bearing = bearing;
+ this.fireChangeEvenet();
+ },
+
+ onCloseClick: function() {
+ this.fire('closeclick');
+ },
+
+ fireChangeEvenet: function() {
+ if (this._node) {
+ const latlon = this._node.originalLatLon;
+ this.fire('change', {
+ latlng: L.latLng(latlon.lat, latlon.lon),
+ heading: this._bearing,
+ pitch: this._pitch,
+ zoom: this._zoom
+ }
+ );
+ }
+ },
+
+ deactivate: function() {
+ this.viewer.activateCover();
+ if (this._updateHandler) {
+ clearInterval(this._updateHandler);
+ this._updateHandler = null;
+ }
+ },
+
+ updateZoomAndCenter: function() {
+ this.viewer.getZoom().then((zoom) => {
+ this._zoom=zoom
+ });
+ this.viewer.getCenter().then((center) => {
+ this._center = center;
+ });
+ this.viewer.getBearing().then((bearing) => {
+ this.onBearingChanged(bearing);
+ });
+ },
+
+ activate: function() {
+ this.viewer.resize();
+ this.viewer.deactivateCover();
+ if (!this._updateHandler) {
+ this._updateHandler = setInterval(() => this.updateZoomAndCenter(), 200);
+ }
+ },
+
+ getState: function() {
+ if (!this._node) {
+ return [];
+ }
+ const {lat, lon} = this._node.originalLatLon;
+ return [lat.toFixed(6), lon.toFixed(6),
+ this._center[0].toFixed(4), this._center[1].toFixed(4), this._zoom.toFixed(2)];
+ },
+
+ setState: function(state) {
+ const lat = parseFloat(state[0]);
+ const lng = parseFloat(state[1]);
+ const center0 = parseFloat(state[2]);
+ const center1 = parseFloat(state[3]);
+ const zoom = parseFloat(state[4]);
+ if (!isNaN(lat) && !isNaN(lng) && !isNaN(center0) && !isNaN(center1) && !isNaN(zoom)) {
+ this.viewer.moveCloseTo(lat, lng).then(() => {
+ this.viewer.setCenter([center0, center1]);
+ this.viewer.setZoom(zoom);
+ });
+ return true;
+ }
+ return false;
+ }
+ }
+);
+
+
+async function getViewer(container) {
+ const mapillary = await getMapillary();
+ return new Viewer(mapillary, container);
+}
+
+export default {getCoverageLayer, getPanoramaAtPos, getViewer};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.panoramas/lib/mapillary/mapillary-coverage-layer.js b/src/lib/leaflet.control.panoramas/lib/mapillary/mapillary-coverage-layer.js
@@ -0,0 +1,155 @@
+import L from 'leaflet';
+import {MapillaryLoader} from './mapillary-loader';
+
+const MapillaryCoverage = L.GridLayer.extend({
+ options: {
+ tileSize: 1024,
+ updateWhenIdle: true,
+ color: '#00cfb1'
+ },
+
+ initialize: function(options) {
+ L.GridLayer.prototype.initialize.call(this, options);
+ this.loader = new MapillaryLoader(this.options.url, 12);
+ },
+
+ onAdd: function(map) {
+ L.GridLayer.prototype.onAdd.call(this, map);
+ this.on('tileunload', this.onTileUnload, this);
+ },
+
+ onRemove: function(map) {
+ L.GridLayer.prototype.onRemove.call(this, map);
+ this.off('tileunload', this.onTileUnload, this);
+
+ },
+
+ onTileUnload: function(e) {
+ const tile = e.tile;
+ tile._abortLoading();
+ delete tile._tileData;
+ delete tile._adjustment;
+ },
+
+ drawOverview: function(canvas) {
+ const
+ tileData = canvas._tileData;
+ let {multiplier, offsetX, offsetY} = canvas._adjustment;
+ const canvasCtx = canvas.getContext('2d');
+ canvasCtx.fillStyle = this.options.color;
+ for (let feature of tileData['mapillary-sequence-overview']) {
+ if (feature.geometry.type !== 'Point') {
+ throw new Error(`Invalid sequence overview geometry type "${feature.geometry.type}"`)
+ }
+ canvasCtx.beginPath();
+ let x = feature.geometry.coordinates[0] * multiplier - offsetX;
+ let y = feature.geometry.coordinates[1] * multiplier - offsetY;
+ canvasCtx.arc(x, y, 5, 0, 2 * Math.PI);
+ canvasCtx.fill();
+ }
+
+ },
+
+ drawSequences: function(canvas, lineWidth) {
+ let
+ tileData = canvas._tileData,
+ adjustment = canvas._adjustment;
+
+ const canvasCtx = canvas.getContext('2d');
+ canvasCtx.beginPath();
+ canvasCtx.strokeStyle = this.options.color;
+ canvasCtx.lineWidth = lineWidth;
+ // canvasCtx.lineWidth = thinLines ? 1 : 1;
+ canvasCtx.lineCap = "round";
+ canvasCtx.lineJoin = "bevel";
+ for (let feature of tileData['mapillary-sequences']) {
+ if (feature.geometry.type !== 'MultiLineString') {
+ throw new Error(`Invalid sequence geometry type "${feature.geometry.type}"`)
+ }
+ let {multiplier, offsetX, offsetY} = adjustment;
+
+ let lines = feature.geometry.coordinates;
+ for (let lineI = 0; lineI < lines.length; lineI++) {
+ let line = lines[lineI];
+ if (!line.length) {
+ continue;
+ }
+ let x = line[0][0] * multiplier - offsetX;
+ let y = line[0][1] * multiplier - offsetY;
+ canvasCtx.moveTo(x, y);
+ if (line.length === 1) {
+ canvasCtx.lineTo(x, y);
+ }
+ for (let pointI = 0; pointI < line.length; pointI++) {
+ let x = line[pointI][0] * multiplier - offsetX;
+ let y = line[pointI][1] * multiplier - offsetY;
+ canvasCtx.lineTo(x, y);
+ }
+ }
+ }
+ canvasCtx.stroke();
+ },
+
+ drawImages: function(canvas) {
+ let
+ tileData = canvas._tileData,
+ adjustment = canvas._adjustment;
+ let {multiplier, offsetX, offsetY} = adjustment;
+ const canvasCtx = canvas.getContext('2d');
+ canvasCtx.beginPath();
+ canvasCtx.fillStyle = this.options.color;
+ for (let feature of tileData['mapillary-images']) {
+ if (feature.geometry.type !== 'Point') {
+ throw new Error(`Invalid image geometry type "${feature.geometry.type}"`)
+ }
+ canvasCtx.beginPath();
+ let x = feature.geometry.coordinates[0] * multiplier - offsetX;
+ let y = feature.geometry.coordinates[1] * multiplier - offsetY;
+ canvasCtx.arc(x, y, 4, 0, 2 * Math.PI);
+ canvasCtx.fill();
+ }
+
+ },
+
+ drawTile: function(canvas, coords) {
+ if (!this._map) {
+ return;
+ }
+ if (!canvas._tileData) {
+ return;
+ }
+ if (coords.z < 6 + 2 ) {
+ this.drawOverview(canvas);
+ } else if (coords.z < 14 + 2) {
+ let width = coords.z < 14 ? 10 : 5;
+ this.drawSequences(canvas, width);
+ } else {
+ this.drawSequences(canvas, 2);
+ this.drawImages(canvas)
+ }
+
+ },
+
+ createTile: function(coords, done) {
+ const canvas = L.DomUtil.create('canvas', 'leaflet-tile');
+ canvas.width = this.options.tileSize;
+ canvas.height = this.options.tileSize;
+ let {dataPromise, abortLoading} = this.loader.requestTileData(coords);
+ dataPromise.then((data) => {
+ canvas._tileData = data.tileData;
+ canvas._adjustment = data.adjustment || {multiplier: 1, offsetX: 0, offsetY: 0};
+ setTimeout(() => {
+ this.drawTile(canvas, coords);
+ done(null, canvas);
+ }, 1);
+ }
+ );
+
+ canvas._abortLoading = abortLoading;
+ return canvas;
+ },
+
+ }
+);
+
+export {MapillaryCoverage};
diff --git a/src/lib/leaflet.control.panoramas/lib/mapillary/mapillary-loader.js b/src/lib/leaflet.control.panoramas/lib/mapillary/mapillary-loader.js
@@ -0,0 +1,89 @@
+import L from 'leaflet';
+import {TiledDataLoader} from 'lib/tiled-data-loader';
+import {decodeMvt} from './mvt';
+
+
+class MapillaryLoader extends TiledDataLoader {
+ url = 'https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt';
+ maxZoom = 14;
+
+ getTileUrl(coords) {
+ const data = {
+ x: coords.x,
+ z: coords.z,
+ y: coords.y
+ };
+ return L.Util.template(this.url, data);
+ }
+
+ layerTileToDataTileCoords(layerTileCoords) {
+ let z = layerTileCoords.z - 2;
+ let z2 = null;
+ if (z > 6 && z <= 10) {
+ z2 = 6;
+ } else if (z >= 11 && z < 14) {
+ z2 = z - 4;
+ } else if (z < 0) {
+ z2 = 0;
+ } else if (z > this.maxZoom) {
+ z2 = this.maxZoom
+ } else {
+ return {z, x: layerTileCoords.x, y: layerTileCoords.y}
+ }
+
+ let multiplier = 1 << (z - z2);
+ return {
+ x: Math.floor(layerTileCoords.x / multiplier),
+ y: Math.floor(layerTileCoords.y / multiplier),
+ z: z2
+ }
+ }
+
+
+ makeRequestData(dataTileCoords) {
+ return {
+ url: this.getTileUrl(dataTileCoords),
+ options: {
+ responseType: 'arraybuffer',
+ timeout: 10000,
+ isResponseSuccess: (xhr) => xhr.status === 200 || xhr.status === 403
+ }
+ }
+ }
+
+ calcAdjustment(layerTileCoords, dataTileCoords) {
+ let adjustment = super.calcAdjustment(
+ {x: layerTileCoords.x, y: layerTileCoords.y, z: layerTileCoords.z - 2},
+ dataTileCoords
+ );
+ if (adjustment) {
+ adjustment.offsetX *= 1024;
+ adjustment.offsetY *= 1024;
+ }
+ return adjustment;
+ }
+
+ async processResponse(xhr, originalDataTileCoords) {
+ return this._processResponse(xhr, originalDataTileCoords);
+ }
+
+ async _processResponse(xhr, originalDataTileCoords) {
+ let tileData;
+ if (xhr.status === 200 && xhr.response) {
+ const layers = decodeMvt(xhr.response, 1024);
+ tileData = {};
+ for (let layer of layers) {
+ tileData[layer.name] = layer.features;
+ }
+ } else {
+ tileData = null;
+ }
+
+ return {
+ tileData,
+ coords: originalDataTileCoords
+ }
+ }
+}
+
+export {MapillaryLoader};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.panoramas/lib/mapillary/mvt.js b/src/lib/leaflet.control.panoramas/lib/mapillary/mvt.js
@@ -0,0 +1,123 @@
+import Pbf from 'pbf';
+import {Tile as TileProto} from './vector_tile_proto';
+
+function decodeCoordinate(x) {
+ return ((x >> 1) ^ (-(x & 1)));
+}
+
+function parseGeometry(geometryType, ints, coordinatesScale) {
+ if (geometryType !== TileProto.GeomType.POINT && geometryType !== TileProto.GeomType.LINESTRING &&
+ geometryType !== TileProto.GeomType.POLYGON) {
+ throw new Error(`Unknown feature geometry type ${geometryType}`);
+ }
+ const len = ints.length;
+ let pos = 0;
+ const lineStrings = [];
+ let line;
+ let x = 0, y = 0;
+ while (pos < len) {
+ let i = ints[pos];
+ let cmd = i & 0x7;
+ let cmdRepeat = i >> 3;
+ switch (cmd) {
+ case 1: // MoveTo
+ if (cmdRepeat !== 1) {
+ throw new Error(`repeat=${cmdRepeat} for command MoveTo`);
+ }
+ if (pos + 2 > len) {
+ throw new Error('Not enough elements for MoveTo arguments');
+ }
+ if (line) {
+ lineStrings.push(line);
+ }
+ x += decodeCoordinate(ints[pos + 1]);
+ y += decodeCoordinate(ints[pos + 2]);
+ line = [[x * coordinatesScale, y * coordinatesScale]];
+ pos += 3;
+ break;
+ case 2: // LineTo
+ if (cmdRepeat < 1) {
+ throw new Error(`repeat=${cmdRepeat} for command LineTo`);
+ }
+ if (!line) {
+ throw new Error('LineTo with empty linestring')
+ }
+ pos +=1;
+ for (let cmdN = 0; cmdN < cmdRepeat; cmdN++){
+ if (pos + 2 > len) {
+ throw new Error('Not enough elements for LineTo arguments');
+ }
+ x += decodeCoordinate(ints[pos]);
+ y += decodeCoordinate(ints[pos + 1]);
+ line.push([x * coordinatesScale, y * coordinatesScale])
+ pos += 2;
+ }
+ break;
+ case 7: // ClosePath
+ if (geometryType !== TileProto.GeomType.POLYGON) {
+ throw new Error(`ClosePath command for non-polygon type ${geometryType}`);
+ }
+ if (!line) {
+ throw new Error('ClosePath with empty linestring')
+ }
+ if (cmdRepeat !== 1) {
+ throw new Error(`ClosePath repeats ${cmdRepeat} times`);
+ }
+ line.push(line[0]);
+ pos += 1;
+ break;
+ default:
+ throw new Error(`Unknown command ${i} & 0x7 = ${cmd}`);
+ }
+ }
+ if (line) {
+ lineStrings.push(line);
+ }
+ const geometry = {};
+ switch (geometryType) {
+ case TileProto.GeomType.POINT:
+ if (lineStrings.length !== 1 || lineStrings[0].length !== 1) {
+ throw new Error('Invalid coordinates number for point');
+ }
+ geometry.type = 'Point';
+ geometry.coordinates = lineStrings[0][0];
+ break;
+ case TileProto.GeomType.LINESTRING:
+ geometry.type = 'MultiLineString';
+ geometry.coordinates = lineStrings;
+ break;
+ case TileProto.GeomType.POLYGON:
+ geometry.type = 'Polygon';
+ geometry.coordinates = lineStrings;
+ break;
+ default:
+ }
+ return geometry;
+}
+
+
+function parseFeatures(layer, coordinatesScale) {
+ const features = [];
+ for (let feature of layer.features) {
+ const geometry = parseGeometry(feature.type, feature.geometry, coordinatesScale);
+ features.push({geometry});
+ }
+ return features;
+}
+
+function decodeMvt(ar, tileExtent=256) {
+ const
+ pbf = new Pbf(new Uint8Array(ar)),
+ tileData = TileProto.read(pbf);
+ const parsedLayers = [];
+ for (let layer of tileData.layers) {
+ let scale = tileExtent / layer.extent;
+ parsedLayers.push({
+ name: layer.name,
+ features: parseFeatures(layer, scale)
+ });
+ }
+ return parsedLayers;
+}
+
+export {decodeMvt};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.panoramas/lib/mapillary/vector_tile_proto.js b/src/lib/leaflet.control.panoramas/lib/mapillary/vector_tile_proto.js
@@ -0,0 +1,46 @@
+// code generated by pbf v3.0.5
+
+// Tile ========================================
+
+var Tile = exports.Tile = {};
+
+Tile.read = function (pbf, end) {
+ return pbf.readFields(Tile._readField, {layers: []}, end);
+};
+Tile._readField = function (tag, obj, pbf) {
+ if (tag === 3) obj.layers.push(Tile.Layer.read(pbf, pbf.readVarint() + pbf.pos));
+};
+
+Tile.GeomType = {
+ "UNKNOWN": 0,
+ "POINT": 1,
+ "LINESTRING": 2,
+ "POLYGON": 3
+};
+
+
+// Tile.Feature ========================================
+
+Tile.Feature = {};
+
+Tile.Feature.read = function (pbf, end) {
+ return pbf.readFields(Tile.Feature._readField, {id: 0, tags: [], type: 0, geometry: []}, end);
+};
+Tile.Feature._readField = function (tag, obj, pbf) {
+ if (tag === 3) obj.type = pbf.readVarint();
+ else if (tag === 4) pbf.readPackedVarint(obj.geometry);
+};
+
+// Tile.Layer ========================================
+
+Tile.Layer = {};
+
+Tile.Layer.read = function (pbf, end) {
+ return pbf.readFields(Tile.Layer._readField, {version: 0, name: "", features: [], keys: [], values: [], extent: 0}, end);
+};
+Tile.Layer._readField = function (tag, obj, pbf) {
+ if (tag === 15) obj.version = pbf.readVarint();
+ else if (tag === 1) obj.name = pbf.readString();
+ else if (tag === 2) obj.features.push(Tile.Feature.read(pbf, pbf.readVarint() + pbf.pos));
+ else if (tag === 5) obj.extent = pbf.readVarint();
+};
diff --git a/src/lib/leaflet.control.panoramas/style.css b/src/lib/leaflet.control.panoramas/style.css
@@ -1,11 +1,40 @@
-.leaflet-contol-panoramas {
+.icon-panoramas {
background-image: url('panoramas-off.png');
}
-.leaflet-contol-panoramas.enabled {
+.leaflet-contol-panoramas.enabled .icon-panoramas {
background-image: url('panoramas-on.png');
}
+
+.panoramas-list, .panoramas-button {
+ float: left;
+}
+
+.panoramas-list {
+ margin-left: 6px;
+ padding-left: 6px !important;
+ display: none;
+}
+
+.panoramas-list input, .panoramas-list label {
+ vertical-align: bottom;
+}
+
+.leaflet-contol-panoramas.enabled .panoramas-list {
+ display: block;
+}
+
+.panorama-container {
+ width: 100%;
+ height: 100%;
+ display: none;
+}
+
+.panorama-container.enabled {
+ display: block;
+}
+
.leaflet-panorama-marker-wraper {
width: 0 !important;
height: 0 !important;
@@ -23,4 +52,36 @@
.panoramas-control-active {
cursor: pointer;
+}
+
+.mapillary-viewer-date-overlay {
+ position: absolute;
+ top: 0;
+ right: 0;
+ background-color: rgba(0,0,0,.7);
+ font-family: Arial,Helvetica,sans-serif;
+ font-size: 11px;
+ padding: 4px 4px;
+ color: #fff;
+ z-index: 1000;
+}
+
+.mapillary-viewer-button-close {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 40px;
+ height: 40px;
+ background-color: rgba(0,0,0,.7);
+ color: #aaa;
+ z-index: 1000;
+ text-align: center;
+ vertical-align: middle;
+ line-height: 40px;
+ font-family: monospace;
+ font-size: 30px;
+}
+
+.mapillary-viewer-button-close:before {
+ content: "←";
}
\ No newline at end of file
diff --git a/src/lib/leaflet.layer.mapillary/index.js b/src/lib/leaflet.layer.mapillary/index.js
@@ -1 +0,0 @@
-
diff --git a/src/lib/leaflet.layer.mapillary/lib/mvt.js b/src/lib/leaflet.layer.mapillary/lib/mvt.js
@@ -1,148 +0,0 @@
-import Pbf from 'pbf';
-import {Tile as TileProto} from './vector_tile_proto';
-
-function simlifyTagValue(obj) {
- for (let v of Object.values(obj)) {
- if (v) {
- return v;
- }
- }
-}
-
-function decodeCoordinate(x) {
- return ((x >> 1) ^ (-(x & 1)));
-}
-
-function parseGeometry(geometryType, ints, coordinatesScale) {
- if (geometryType !== TileProto.GeomType.POINT && geometryType !== TileProto.GeomType.LINESTRING &&
- geometryType !== TileProto.GeomType.POLYGON) {
- throw new Error(`Unknown feature geometry type ${geometryType}`);
- }
- const len = ints.length;
- let pos = 0;
- const lineStrings = [];
- let line;
- let x = 0, y = 0;
- while (pos < len) {
- let i = ints[pos];
- let cmd = i & 0x7;
- let cmdRepeat = i >> 3;
- switch (cmd) {
- case 1: // MoveTo
- if (cmdRepeat !== 1) {
- throw new Error(`repeat=${cmdRepeat} for command MoveTo`);
- }
- if (pos + 2 > len) {
- throw new Error('Not enough elements for MoveTo arguments');
- }
- if (line) {
- lineStrings.push(line);
- }
- x += decodeCoordinate(ints[pos + 1]);
- y += decodeCoordinate(ints[pos + 2]);
- line = [[x * coordinatesScale, y * coordinatesScale]];
- pos += 3;
- break;
- case 2: // LineTo
- if (cmdRepeat < 1) {
- throw new Error(`repeat=${cmdRepeat} for command LineTo`);
- }
- if (!line) {
- throw new Error('LineTo with empty linestring')
- }
- pos +=1;
- for (let cmdN = 0; cmdN < cmdRepeat; cmdN++){
- if (pos + 2 > len) {
- throw new Error('Not enough elements for LineTo arguments');
- }
- x += decodeCoordinate(ints[pos]);
- y += decodeCoordinate(ints[pos + 1]);
- line.push([x * coordinatesScale, y * coordinatesScale])
- pos += 2;
- }
- break;
- case 7: // ClosePath
- if (geometryType !== TileProto.GeomType.POLYGON) {
- throw new Error(`ClosePath command for non-polygon type ${geometryType}`);
- }
- if (!line) {
- throw new Error('ClosePath with empty linestring')
- }
- if (cmdRepeat !== 1) {
- throw new Error(`ClosePath repeats ${cmdRepeat} times`);
- }
- line.push(line[0]);
- pos += 1;
- break;
- default:
- throw new Error(`Unknown command ${i} & 0x7 = ${cmd}`);
- }
- }
- if (line) {
- lineStrings.push(line);
- }
- const geometry = {};
- switch (geometryType) {
- case TileProto.GeomType.POINT:
- if (lineStrings.length !== 1 || lineStrings[0].length !== 1) {
- console.log(lineStrings);
- throw new Error('Invalid coordinates number for point');
- }
- geometry.type = 'Point';
- geometry.coordinats = lineStrings[0][0];
- break;
- case TileProto.GeomType.LINESTRING:
- if (lineStrings.length !== 1) {
- throw new Error('Invalid linestrings number for line');
- }
- geometry.type = 'LineString';
- geometry.coordinats = lineStrings[0];
- break;
- case TileProto.GeomType.POLYGON:
- geometry.type = 'Polygon';
- geometry.coordinats = lineStrings;
- break;
- }
- return geometry;
-}
-
-function parseFeatureTags(tagsInts, layerKeys, layerValues) {
- const tags = {};
- if (tagsInts) {
- let i = 0;
- while (i < tagsInts.length) {
- let key = layerKeys[tagsInts[i]];
- let value = layerValues[tagsInts[i + 1]];
- tags[key] = value;
- i +=2;
- }
- }
- return tags;
-}
-
-function parseFeatures(layer, coordinatesScale) {
- const features = [];
- const tagValues = layer.values.map(simlifyTagValue);
- for (let feature of layer.features) {
- const properties = parseFeatureTags(feature.tags, layer.keys, tagValues);
- const geometry = parseGeometry(feature.type, feature.geometry, coordinatesScale);
- features.push({properties, geometry});
- }
- return features;
-}
-
-function decodeMvt(ar, tileExtent=256) {
- const
- pbf = new Pbf(new Uint8Array(ar)),
- tileData = TileProto.read(pbf);
- const layers = tileData.layers.map((layer) => {
- const scale = tileExtent / layer.extent;
- return {
- name: layer.name,
- features: parseFeatures(layer, scale)
- }
- });
- return layers;
-}
-
-export {decodeMvt};
-\ No newline at end of file
diff --git a/src/lib/leaflet.layer.mapillary/lib/vector_tile.proto b/src/lib/leaflet.layer.mapillary/lib/vector_tile.proto
@@ -1,78 +0,0 @@
-package vector_tile;
-
-option optimize_for = LITE_RUNTIME;
-
-message Tile {
-
- // GeomType is described in section 4.3.4 of the specification
- enum GeomType {
- UNKNOWN = 0;
- POINT = 1;
- LINESTRING = 2;
- POLYGON = 3;
- }
-
- // Variant type encoding
- // The use of values is described in section 4.1 of the specification
- message Value {
- // Exactly one of these values must be present in a valid message
- optional string string_value = 1;
- optional float float_value = 2;
- optional double double_value = 3;
- optional int64 int_value = 4;
- optional uint64 uint_value = 5;
- optional sint64 sint_value = 6;
- optional bool bool_value = 7;
-
- extensions 8 to max;
- }
-
- // Features are described in section 4.2 of the specification
- message Feature {
- optional uint64 id = 1 [ default = 0 ];
-
- // Tags of this feature are encoded as repeated pairs of
- // integers.
- // A detailed description of tags is located in sections
- // 4.2 and 4.4 of the specification
- repeated uint32 tags = 2 [ packed = true ];
-
- // The type of geometry stored in this feature.
- optional GeomType type = 3 [ default = UNKNOWN ];
-
- // Contains a stream of commands and parameters (vertices).
- // A detailed description on geometry encoding is located in
- // section 4.3 of the specification.
- repeated uint32 geometry = 4 [ packed = true ];
- }
-
- // Layers are described in section 4.1 of the specification
- message Layer {
- // Any compliant implementation must first read the version
- // number encoded in this message and choose the correct
- // implementation for this version number before proceeding to
- // decode other parts of this message.
- required uint32 version = 15 [ default = 1 ];
-
- required string name = 1;
-
- // The actual features in this tile.
- repeated Feature features = 2;
-
- // Dictionary encoding for keys
- repeated string keys = 3;
-
- // Dictionary encoding for values
- repeated Value values = 4;
-
- // Although this is an "optional" field it is required by the specification.
- // See https://github.com/mapbox/vector-tile-spec/issues/47
- optional uint32 extent = 5 [ default = 4096 ];
-
- extensions 16 to max;
- }
-
- repeated Layer layers = 3;
-
- extensions 16 to 8191;
-}
diff --git a/src/lib/leaflet.layer.mapillary/lib/vector_tile_proto.js b/src/lib/leaflet.layer.mapillary/lib/vector_tile_proto.js
@@ -1,66 +0,0 @@
-'use strict'; // code generated by pbf v3.0.5
-
-// Tile ========================================
-
-var Tile = exports.Tile = {};
-
-Tile.read = function (pbf, end) {
- return pbf.readFields(Tile._readField, {layers: []}, end);
-};
-Tile._readField = function (tag, obj, pbf) {
- if (tag === 3) obj.layers.push(Tile.Layer.read(pbf, pbf.readVarint() + pbf.pos));
-};
-
-Tile.GeomType = {
- "UNKNOWN": 0,
- "POINT": 1,
- "LINESTRING": 2,
- "POLYGON": 3
-};
-
-// Tile.Value ========================================
-
-Tile.Value = {};
-
-Tile.Value.read = function (pbf, end) {
- return pbf.readFields(Tile.Value._readField, {string_value: "", float_value: 0, double_value: 0, int_value: 0, uint_value: 0, sint_value: 0, bool_value: false}, end);
-};
-Tile.Value._readField = function (tag, obj, pbf) {
- if (tag === 1) obj.string_value = pbf.readString();
- else if (tag === 2) obj.float_value = pbf.readFloat();
- else if (tag === 3) obj.double_value = pbf.readDouble();
- else if (tag === 4) obj.int_value = pbf.readVarint(true);
- else if (tag === 5) obj.uint_value = pbf.readVarint();
- else if (tag === 6) obj.sint_value = pbf.readSVarint();
- else if (tag === 7) obj.bool_value = pbf.readBoolean();
-};
-
-// Tile.Feature ========================================
-
-Tile.Feature = {};
-
-Tile.Feature.read = function (pbf, end) {
- return pbf.readFields(Tile.Feature._readField, {id: 0, tags: [], type: 0, geometry: []}, end);
-};
-Tile.Feature._readField = function (tag, obj, pbf) {
- if (tag === 1) obj.id = pbf.readVarint();
- else if (tag === 2) pbf.readPackedVarint(obj.tags);
- else if (tag === 3) obj.type = pbf.readVarint();
- else if (tag === 4) pbf.readPackedVarint(obj.geometry);
-};
-
-// Tile.Layer ========================================
-
-Tile.Layer = {};
-
-Tile.Layer.read = function (pbf, end) {
- return pbf.readFields(Tile.Layer._readField, {version: 0, name: "", features: [], keys: [], values: [], extent: 0}, end);
-};
-Tile.Layer._readField = function (tag, obj, pbf) {
- if (tag === 15) obj.version = pbf.readVarint();
- else if (tag === 1) obj.name = pbf.readString();
- else if (tag === 2) obj.features.push(Tile.Feature.read(pbf, pbf.readVarint() + pbf.pos));
- else if (tag === 3) obj.keys.push(pbf.readString());
- else if (tag === 4) obj.values.push(Tile.Value.read(pbf, pbf.readVarint() + pbf.pos));
- else if (tag === 5) obj.extent = pbf.readVarint();
-};