mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-22 08:17:30 +02:00
add hover and click event
This commit is contained in:
+2
-1
@@ -251,7 +251,8 @@
|
||||
.layer-mapilio .viewfield-group * {
|
||||
fill: #0056f1;
|
||||
stroke: #ffffff;
|
||||
fill-opacity: .7;
|
||||
stroke-opacity: .6;
|
||||
fill-opacity: .6;
|
||||
}
|
||||
.layer-mapilio .sequence {
|
||||
stroke: #0056f1;
|
||||
|
||||
@@ -1432,6 +1432,9 @@ en:
|
||||
kartaview:
|
||||
title: KartaView
|
||||
view_on_kartaview: "View this image on KartaView"
|
||||
mapilio:
|
||||
title: Mapilio
|
||||
tooltip: "Street-level photos from Mapilio"
|
||||
note:
|
||||
note: Note
|
||||
title: Edit note
|
||||
|
||||
+223
-10
@@ -1,4 +1,5 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import Protobuf from 'pbf';
|
||||
import RBush from 'rbush';
|
||||
@@ -7,16 +8,32 @@ import { VectorTile } from '@mapbox/vector-tile';
|
||||
import { utilRebind, utilTiler } from '../util';
|
||||
import {geoExtent, geoScaleToZoom} from '../geo';
|
||||
|
||||
const apiUrl = 'https://end.mapilio.com';
|
||||
const imageBaseUrl = 'https://cdn.mapilio.com/im';
|
||||
const baseTileUrl = 'https://geo.mapilio.com/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=mapilio:';
|
||||
const pointLayer = 'points_mapilio_map';
|
||||
const lineLayer = 'captured_roads_line';
|
||||
const tileStyle = '&STYLE=&TILEMATRIX=EPSG:900913:{z}&TILEMATRIXSET=EPSG:900913&FORMAT=application/vnd.mapbox-vector-tile&TILECOL={x}&TILEROW={y}';
|
||||
|
||||
const minZoom = 14;
|
||||
const dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'imageChanged');
|
||||
const dispatch = d3_dispatch('change', 'loadedImages', 'loadedLine');
|
||||
const pannellumViewerCSS = 'pannellum-streetside/pannellum.css';
|
||||
const pannellumViewerJS = 'pannellum-streetside/pannellum.js';
|
||||
const resolution = 1080;
|
||||
|
||||
let _mlyActiveImage;
|
||||
let _mlyCache;
|
||||
let _loadViewerPromise;
|
||||
let _pannellumViewer;
|
||||
let _mlySceneOptions = {
|
||||
showFullscreenCtrl: false,
|
||||
autoLoad: true,
|
||||
yaw: 0,
|
||||
minHfov: 10,
|
||||
maxHfov: 90,
|
||||
hfov: 60,
|
||||
};
|
||||
let _currScene = 0;
|
||||
|
||||
|
||||
// Partition viewport into higher zoom tiles
|
||||
@@ -84,6 +101,8 @@ function loadTile(which, url, tile) {
|
||||
|
||||
if (which === 'images') {
|
||||
dispatch.call('loadedImages');
|
||||
} else {
|
||||
dispatch.call('loadedLines');
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
@@ -94,7 +113,7 @@ function loadTile(which, url, tile) {
|
||||
|
||||
|
||||
// Load the data from the vector tile into cache
|
||||
function loadTileDataToCache(data, tile, which) {
|
||||
function loadTileDataToCache(data, tile) {
|
||||
const vectorTile = new VectorTile(new Protobuf(data));
|
||||
let features,
|
||||
cache,
|
||||
@@ -113,10 +132,10 @@ function loadTileDataToCache(data, tile, which) {
|
||||
loc = feature.geometry.coordinates;
|
||||
d = {
|
||||
loc: loc,
|
||||
captured_at: feature.properties.captured_at,
|
||||
created_at: feature.properties.created_at,
|
||||
id: feature.properties.id,
|
||||
sequence_id: feature.properties.sequence_uuid,
|
||||
heading:feature.properties.heading
|
||||
};
|
||||
cache.forImageId[d.id] = d;
|
||||
features.push({
|
||||
@@ -129,7 +148,6 @@ function loadTileDataToCache(data, tile, which) {
|
||||
}
|
||||
|
||||
if (vectorTile.layers.hasOwnProperty('captured_roads_line')) {
|
||||
features = [];
|
||||
cache = _mlyCache.sequences;
|
||||
layer = vectorTile.layers.captured_roads_line;
|
||||
|
||||
@@ -145,6 +163,22 @@ function loadTileDataToCache(data, tile, which) {
|
||||
|
||||
}
|
||||
|
||||
function getImageData(imageId, sequenceId) {
|
||||
|
||||
return fetch(apiUrl + `/api/sequence-detail?sequence_uuid=${sequenceId}`, {method: 'GET'})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
let index = data.data.findIndex((feature) => feature.id === imageId);
|
||||
const {filename, uploaded_hash} = data.data[index];
|
||||
_mlySceneOptions.panorama = imageBaseUrl + '/' + uploaded_hash + '/' + filename + '/' + resolution;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
// Initialize Mapilio
|
||||
@@ -177,6 +211,10 @@ export default {
|
||||
return searchLimited(limit, projection, _mlyCache.images.rtree);
|
||||
},
|
||||
|
||||
cachedImage: function(imageKey) {
|
||||
return _mlyCache.images.forImageId[imageKey];
|
||||
},
|
||||
|
||||
|
||||
// Load images in the visible area
|
||||
loadImages: function(projection) {
|
||||
@@ -215,24 +253,199 @@ export default {
|
||||
return lineStrings;
|
||||
},
|
||||
|
||||
// Set the currently visible image
|
||||
setActiveImage: function(image) {
|
||||
if (image) {
|
||||
_mlyActiveImage = {
|
||||
id: image.id,
|
||||
sequence_id: image.sequence_id
|
||||
};
|
||||
} else {
|
||||
_mlyActiveImage = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Update the currently highlighted sequence and selected bubble.
|
||||
setStyles: function(context, hovered) {
|
||||
const hoveredImageId = hovered && hovered.id;
|
||||
const hoveredSequenceId = hovered && hovered.sequence_id;
|
||||
const selectedSequenceId = _mlyActiveImage && _mlyActiveImage.sequence_id;
|
||||
const selectedImageId = _mlyActiveImage && _mlyActiveImage.id;
|
||||
|
||||
context.container().selectAll('.layer-mapilio .viewfield-group')
|
||||
.classed('highlighted', function(d) { return (d.sequence_id === selectedSequenceId) || (d.id === hoveredImageId); })
|
||||
.classed('hovered', function(d) { return d.id === hoveredImageId; });
|
||||
const markers = context.container().selectAll('.layer-mapilio .viewfield-group');
|
||||
const sequences = context.container().selectAll('.layer-mapilio .sequence');
|
||||
|
||||
context.container().selectAll('.layer-mapilio .sequence')
|
||||
.classed('highlighted', function(d) { return d.properties.id === hoveredSequenceId; })
|
||||
.classed('currentView', function(d) { return d.properties.id === selectedSequenceId; });
|
||||
markers.classed('highlighted', function(d) { return d.id === hoveredImageId; })
|
||||
.classed('hovered', function(d) { return d.id === hoveredImageId; })
|
||||
.classed('currentView', function(d) { return d.id === selectedImageId; });
|
||||
|
||||
sequences.classed('highlighted', function(d) { return d.properties.sequence_uuid === hoveredSequenceId; })
|
||||
.classed('currentView', function(d) { return d.properties.sequence_uuid === selectedSequenceId; });
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
initViewer: function () {
|
||||
if (!window.pannellum) return;
|
||||
if (_pannellumViewer) return;
|
||||
|
||||
_currScene += 1;
|
||||
const sceneID = _currScene.toString();
|
||||
const options = {
|
||||
'default': { firstScene: sceneID },
|
||||
scenes: {}
|
||||
};
|
||||
options.scenes[sceneID] = _mlySceneOptions;
|
||||
|
||||
_pannellumViewer = window.pannellum.viewer('ideditor-viewer-mapilio', options);
|
||||
},
|
||||
|
||||
selectImage: function (context, id) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let d = this.cachedImage(id);
|
||||
|
||||
this.setActiveImage(d);
|
||||
|
||||
let viewer = context.container().select('.photoviewer');
|
||||
if (!viewer.empty()) viewer.datum(d);
|
||||
|
||||
this.setStyles(context, null);
|
||||
|
||||
if (!d) return this;
|
||||
|
||||
getImageData(d.id,d.sequence_id).then(function () {
|
||||
|
||||
if (!_pannellumViewer) {
|
||||
that.initViewer();
|
||||
} else {
|
||||
// make a new scene
|
||||
_currScene += 1;
|
||||
let sceneID = _currScene.toString();
|
||||
_pannellumViewer
|
||||
.addScene(sceneID, _mlySceneOptions)
|
||||
.loadScene(sceneID);
|
||||
|
||||
// remove previous scene
|
||||
if (_currScene > 2) {
|
||||
sceneID = (_currScene - 1).toString();
|
||||
_pannellumViewer
|
||||
.removeScene(sceneID);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
ensureViewerLoaded: function(context) {
|
||||
if (_loadViewerPromise) return _loadViewerPromise;
|
||||
|
||||
let wrap = context.container().select('.photoviewer').selectAll('.mapilio-wrapper')
|
||||
.data([0]);
|
||||
|
||||
let wrapEnter = wrap.enter()
|
||||
.append('div')
|
||||
.attr('class', 'photo-wrapper mapilio-wrapper')
|
||||
.classed('hide', true);
|
||||
|
||||
wrapEnter
|
||||
.append('div')
|
||||
.attr('id', 'ideditor-viewer-mapilio');
|
||||
|
||||
|
||||
// Register viewer resize handler
|
||||
context.ui().photoviewer.on('resize.mapilio', () => {
|
||||
if (_pannellumViewer) {
|
||||
_pannellumViewer.resize();
|
||||
}
|
||||
});
|
||||
|
||||
_loadViewerPromise = new Promise((resolve, reject) => {
|
||||
let loadedCount = 0;
|
||||
function loaded() {
|
||||
loadedCount += 1;
|
||||
|
||||
// wait until both files are loaded
|
||||
if (loadedCount === 2) resolve();
|
||||
}
|
||||
|
||||
const head = d3_select('head');
|
||||
|
||||
// load pannellum-viewercss
|
||||
head.selectAll('#ideditor-mapilio-viewercss')
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('link')
|
||||
.attr('id', 'ideditor-mapilio-viewercss')
|
||||
.attr('rel', 'stylesheet')
|
||||
.attr('crossorigin', 'anonymous')
|
||||
.attr('href', context.asset(pannellumViewerCSS))
|
||||
.on('load.serviceMapilio', loaded)
|
||||
.on('error.serviceMapilio', function() {
|
||||
reject();
|
||||
});
|
||||
|
||||
// load pannellum-viewerjs
|
||||
head.selectAll('#ideditor-mapilio-viewerjs')
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('script')
|
||||
.attr('id', 'ideditor-mapilio-viewerjs')
|
||||
.attr('crossorigin', 'anonymous')
|
||||
.attr('src', context.asset(pannellumViewerJS))
|
||||
.on('load.serviceMapilio', loaded)
|
||||
.on('error.serviceMapilio', function() {
|
||||
reject();
|
||||
});
|
||||
})
|
||||
.catch(function() {
|
||||
_loadViewerPromise = null;
|
||||
});
|
||||
|
||||
return _loadViewerPromise;
|
||||
},
|
||||
|
||||
showViewer:function (context) {
|
||||
let wrap = context.container().select('.photoviewer')
|
||||
.classed('hide', false);
|
||||
|
||||
let isHidden = wrap.selectAll('.photo-wrapper.mapilio-wrapper.hide').size();
|
||||
|
||||
if (isHidden) {
|
||||
wrap
|
||||
.selectAll('.photo-wrapper:not(.mapilio-wrapper)')
|
||||
.classed('hide', true);
|
||||
|
||||
wrap
|
||||
.selectAll('.photo-wrapper.mapilio-wrapper')
|
||||
.classed('hide', false);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* hideViewer()
|
||||
*/
|
||||
hideViewer: function (context) {
|
||||
let viewer = context.container().select('.photoviewer');
|
||||
if (!viewer.empty()) viewer.datum(null);
|
||||
|
||||
viewer
|
||||
.classed('hide', true)
|
||||
.selectAll('.photo-wrapper')
|
||||
.classed('hide', true);
|
||||
|
||||
context.container().selectAll('.viewfield-group, .sequence, .icon-sign')
|
||||
.classed('currentView', false);
|
||||
|
||||
this.setActiveImage();
|
||||
|
||||
return this.setStyles(context, null);
|
||||
},
|
||||
|
||||
// Return the current cache
|
||||
cache: function() {
|
||||
|
||||
@@ -10,6 +10,7 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
const minZoom = 12;
|
||||
let layer = d3_select(null);
|
||||
let _mapilio;
|
||||
const viewFieldZoomLevel = 18;
|
||||
|
||||
|
||||
function init() {
|
||||
@@ -58,8 +59,8 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
|
||||
function transform(d) {
|
||||
let t = svgPointTransform(projection)(d);
|
||||
if (d.ca) {
|
||||
t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
|
||||
if (d.heading) {
|
||||
t += ' rotate(' + Math.floor(d.heading) + ',0,0)';
|
||||
}
|
||||
return t;
|
||||
}
|
||||
@@ -75,9 +76,36 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
layer.style('display', 'none');
|
||||
}
|
||||
|
||||
function click(d3_event, image) {
|
||||
const service = getService();
|
||||
if (!service) return;
|
||||
|
||||
service
|
||||
.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.selectImage(context, image.id)
|
||||
.showViewer(context);
|
||||
});
|
||||
|
||||
context.map().centerEase(image.loc);
|
||||
}
|
||||
|
||||
function mouseover(d3_event, image) {
|
||||
const service = getService();
|
||||
if (service) service.setStyles(context, image);
|
||||
}
|
||||
|
||||
|
||||
function mouseout() {
|
||||
const service = getService();
|
||||
if (service) service.setStyles(context, null);
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
||||
const z = ~~context.map().zoom();
|
||||
const showViewfields = (z >= viewFieldZoomLevel);
|
||||
|
||||
const service = getService();
|
||||
let sequences = (service ? service.sequences(projection) : []);
|
||||
@@ -92,9 +120,8 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
// exit
|
||||
traces.exit()
|
||||
.remove();
|
||||
//
|
||||
// // enter/update
|
||||
traces = traces.enter()
|
||||
|
||||
traces.enter()
|
||||
.append('path')
|
||||
.attr('class', 'sequence')
|
||||
.merge(traces)
|
||||
@@ -111,7 +138,10 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
// enter
|
||||
const groupsEnter = groups.enter()
|
||||
.append('g')
|
||||
.attr('class', 'viewfield-group');
|
||||
.attr('class', 'viewfield-group')
|
||||
.on('mouseenter', mouseover)
|
||||
.on('mouseleave', mouseout)
|
||||
.on('click', click);
|
||||
|
||||
groupsEnter
|
||||
.append('g')
|
||||
@@ -136,15 +166,24 @@ export function svgMapilioImages(projection, context, dispatch) {
|
||||
.attr('r', '6');
|
||||
|
||||
const viewfields = markers.selectAll('.viewfield')
|
||||
.data([0]);
|
||||
.data(showViewfields ? [0] : []);
|
||||
|
||||
viewfields.exit()
|
||||
.remove();
|
||||
|
||||
viewfields.enter() // viewfields may or may not be drawn...
|
||||
.insert('path', 'circle') // but if they are, draw below the circles
|
||||
viewfields.enter()
|
||||
.insert('path', 'circle')
|
||||
.attr('class', 'viewfield')
|
||||
.attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
function viewfieldPath() {
|
||||
if (this.parentNode.__data__.is_pano) {
|
||||
return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0';
|
||||
} else {
|
||||
return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export function uiPhotoviewer(context) {
|
||||
if (services.streetside) { services.streetside.hideViewer(context); }
|
||||
if (services.mapillary) { services.mapillary.hideViewer(context); }
|
||||
if (services.kartaview) { services.kartaview.hideViewer(context); }
|
||||
if (services.mapilio) { services.mapilio.hideViewer(context); }
|
||||
})
|
||||
.append('div')
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
Reference in New Issue
Block a user