nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 51a8993c826ae8bd0cec2ae78602b6e17828a836
parent 9c057604c8b760645e811acb5aaedcea5954d2f6
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri,  7 Jul 2017 01:07:32 +0300

[panoramas] refacored, added mapillary

Diffstat:
Mconfig/webpack.config.prod.js | 4++--
Mpackage.json | 1+
Msrc/App.js | 3++-
Msrc/lib/controls-styles/controls-styles.css | 2+-
Msrc/lib/leaflet.control.panoramas/index.js | 391+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Asrc/lib/leaflet.control.panoramas/lib/google/index.js | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.panoramas/lib/mapillary/index.js | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.panoramas/lib/mapillary/mapillary-coverage-layer.js | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.panoramas/lib/mapillary/mapillary-loader.js | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.panoramas/lib/mapillary/mvt.js | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.control.panoramas/lib/mapillary/vector_tile_proto.js | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.panoramas/style.css | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Dsrc/lib/leaflet.layer.mapillary/index.js | 1-
Dsrc/lib/leaflet.layer.mapillary/lib/mvt.js | 149-------------------------------------------------------------------------------
Dsrc/lib/leaflet.layer.mapillary/lib/vector_tile.proto | 78------------------------------------------------------------------------------
Dsrc/lib/leaflet.layer.mapillary/lib/vector_tile_proto.js | 66------------------------------------------------------------------
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(); -};