Merge branch 'develop' into vegbilder

This commit is contained in:
Martin Raifer
2023-08-01 17:09:21 +02:00
23 changed files with 958 additions and 13 deletions
+6
View File
@@ -37,8 +37,11 @@ _Breaking developer changes, which may affect downstream projects or sites that
# Unreleased (2.27.0-dev)
#### :mega: Release Highlights
* Add [_Mapilio_](https://mapilio.com/openstreetmap) as new street-level imagery provider ([#9664], thanks [@channel-s])
#### :tada: New Features
#### :sparkles: Usability & Accessibility
* Show tag reference information for the currently filled-in tag value in UI fields (if available), instead of only showing the more generic _key_ documentation of the field ([#9786])
#### :scissors: Operations
#### :camera: Street-Level
#### :white_check_mark: Validation
@@ -53,6 +56,9 @@ _Breaking developer changes, which may affect downstream projects or sites that
#### :hammer: Development
[#8997]: https://github.com/openstreetmap/iD/issues/8997
[#9786]: https://github.com/openstreetmap/iD/issues/9786
[#9664]: https://github.com/openstreetmap/iD/pull/9664
[@channel-s]: https://github.com/channel-s
# 2.26.2
+44
View File
@@ -254,6 +254,46 @@
stroke: #20c4ff;
}
/* Mapilio Image Layer */
.layer-mapilio {
pointer-events: none;
}
.layer-mapilio .viewfield-group * {
fill: #0056f1;
stroke: #ffffff;
stroke-opacity: .6;
fill-opacity: .6;
}
.layer-mapilio .sequence {
stroke: #0056f1;
}
.photo-controls-mapilio {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.photo-controls-mapilio button {
padding:0 6px;
pointer-events: initial;
}
.ideditor .mapilio-wrapper {
position: relative;
background-color: #000;
background-image: url(img/loader-black.gif);
background-position: center;
background-repeat: no-repeat;
}
#ideditor-viewer-mapilio-simple-wrap {
height: 100%;
}
#ideditor-viewer-mapilio-simple {
width: 100%;
height: 100%;
transform-origin: 0 0;
}
/* Streetside Viewer (pannellum) */
.ms-wrapper .photo-attribution .image-link {
@@ -306,6 +346,10 @@ label.streetside-hires {
margin: 0 5px;
}
.pnlm-zoom-controls {
margin-top: 6px;
}
/* Mapillary viewer */
#ideditor-mly .domRenderer .TagSymbol {
+8 -3
View File
@@ -2730,14 +2730,19 @@ button.raw-tag-option svg.icon {
}
.tag-reference-body.expanded {
padding-bottom: 10px;
display: inline-block;
padding-left: 10px;
display: block;
}
.tag-reference-description {
.ideditor[dir='rtl'] .tag-reference-body.expanded {
padding-left: 0;
padding-right: 10px;
}
.tag-reference-link {
display: block;
}
.tag-reference-link .icon:first-child {
margin-left: 0;
}
img.tag-reference-wiki-image {
float: right;
+3
View File
@@ -1437,6 +1437,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
+2 -1
View File
@@ -252,7 +252,8 @@ export function rendererBackground(context) {
'mapillary-map-features': 'Mapillary Map Features',
'mapillary-signs': 'Mapillary Signs',
kartaview: 'KartaView Images',
vegbilder: 'Norwegian road administration images'
vegbilder: 'Norwegian Road Administration Images',
mapilio: 'Mapilio Images'
};
for (let layerID in photoOverlayLayers) {
+1 -1
View File
@@ -7,7 +7,7 @@ import { utilQsString, utilStringQs } from '../util';
export function rendererPhotos(context) {
var dispatch = d3_dispatch('change');
var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'kartaview', 'vegbilder'];
var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'kartaview', 'mapilio', 'vegbilder'];
var _allPhotoTypes = ['flat', 'panoramic'];
var _shownPhotoTypes = _allPhotoTypes.slice(); // shallow copy
var _dateFilters = ['fromDate', 'toDate'];
+5 -2
View File
@@ -14,6 +14,7 @@ import serviceTaginfo from './taginfo';
import serviceVectorTile from './vector_tile';
import serviceWikidata from './wikidata';
import serviceWikipedia from './wikipedia';
import serviceMapilio from './mapilio';
export let services = {
@@ -32,7 +33,8 @@ export let services = {
taginfo: serviceTaginfo,
vectorTile: serviceVectorTile,
wikidata: serviceWikidata,
wikipedia: serviceWikipedia
wikipedia: serviceWikipedia,
mapilio: serviceMapilio
};
export {
@@ -51,5 +53,6 @@ export {
serviceTaginfo,
serviceVectorTile,
serviceWikidata,
serviceWikipedia
serviceWikipedia,
serviceMapilio
};
+610
View File
@@ -0,0 +1,610 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom';
import Protobuf from 'pbf';
import RBush from 'rbush';
import { VectorTile } from '@mapbox/vector-tile';
import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } from '../util';
import {geoExtent, geoScaleToZoom} from '../geo';
import {localizer} from '../core/localizer';
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 = 'map_points';
const lineLayer = 'map_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('loadedImages', 'loadedLines');
const imgZoom = d3_zoom()
.extent([[0, 0], [320, 240]])
.translateExtent([[0, 0], [320, 240]])
.scaleExtent([1, 15]);
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
function partitionViewport(projection) {
const z = geoScaleToZoom(projection.scale());
const z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5
const tiler = utilTiler().zoomExtent([z2, z2]);
return tiler.getTiles(projection)
.map(function(tile) { return tile.extent; });
}
// Return no more than `limit` results per partition.
function searchLimited(limit, projection, rtree) {
limit = limit || 5;
return partitionViewport(projection)
.reduce(function(result, extent) {
const found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
return (found.length ? result.concat(found) : result);
}, []);
}
// Load all data for the specified type from Mapilio vector tiles
function loadTiles(which, url, maxZoom, projection) {
const tiler = utilTiler().zoomExtent([minZoom, maxZoom]).skipNullIsland(true);
const tiles = tiler.getTiles(projection);
tiles.forEach(function(tile) {
loadTile(which, url, tile);
});
}
// Load all data for the specified type from one vector tile
function loadTile(which, url, tile) {
const cache = _mlyCache.requests;
const tileId = `${tile.id}-${which}`;
if (cache.loaded[tileId] || cache.inflight[tileId]) return;
const controller = new AbortController();
cache.inflight[tileId] = controller;
const requestUrl = url.replace('{x}', tile.xyz[0])
.replace('{y}', tile.xyz[1])
.replace('{z}', tile.xyz[2]);
fetch(requestUrl, { signal: controller.signal })
.then(function(response) {
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
cache.loaded[tileId] = true;
delete cache.inflight[tileId];
return response.arrayBuffer();
})
.then(function(data) {
if (data.byteLength === 0) {
throw new Error('No Data');
}
loadTileDataToCache(data, tile, which);
if (which === 'images') {
dispatch.call('loadedImages');
} else {
dispatch.call('loadedLines');
}
})
.catch(function (e) {
if (e.message === 'No Data') {
cache.loaded[tileId] = true;
} else {
console.error(e); // eslint-disable-line no-console
}
});
}
// Load the data from the vector tile into cache
function loadTileDataToCache(data, tile) {
const vectorTile = new VectorTile(new Protobuf(data));
let features,
cache,
layer,
i,
feature,
loc,
d;
if (vectorTile.layers.hasOwnProperty(pointLayer)) {
features = [];
cache = _mlyCache.images;
layer = vectorTile.layers[pointLayer];
for (i = 0; i < layer.length; i++) {
feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
loc = feature.geometry.coordinates;
let resolutionArr = feature.properties.resolution.split('x');
let sourceWidth = Math.max(resolutionArr[0], resolutionArr[1]);
let sourceHeight = Math.min(resolutionArr[0] ,resolutionArr[1]);
let isPano = sourceWidth % sourceHeight === 0;
d = {
loc: loc,
capture_time: feature.properties.capture_time,
id: feature.properties.id,
sequence_id: feature.properties.sequence_uuid,
heading: feature.properties.heading,
resolution: feature.properties.resolution,
isPano: isPano
};
cache.forImageId[d.id] = d;
features.push({
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
});
}
if (cache.rtree) {
cache.rtree.load(features);
}
}
if (vectorTile.layers.hasOwnProperty(lineLayer)) {
cache = _mlyCache.sequences;
layer = vectorTile.layers[lineLayer];
for (i = 0; i < layer.length; i++) {
feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
if (cache.lineString[feature.properties.sequence_uuid]) {
cache.lineString[feature.properties.sequence_uuid].push(feature);
} else {
cache.lineString[feature.properties.sequence_uuid] = [feature];
}
}
}
}
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
init: function() {
if (!_mlyCache) {
this.reset();
}
this.event = utilRebind(this, dispatch, 'on');
},
// Reset cache and state
reset: function() {
if (_mlyCache) {
Object.values(_mlyCache.requests.inflight).forEach(function(request) { request.abort(); });
}
_mlyCache = {
images: { rtree: new RBush(), forImageId: {} },
sequences: { rtree: new RBush(), lineString: {} },
requests: { loaded: {}, inflight: {} }
};
_mlyActiveImage = null;
},
// Get visible images
images: function(projection) {
const limit = 5;
return searchLimited(limit, projection, _mlyCache.images.rtree);
},
cachedImage: function(imageKey) {
return _mlyCache.images.forImageId[imageKey];
},
// Load images in the visible area
loadImages: function(projection) {
let url = baseTileUrl + pointLayer + tileStyle;
loadTiles('images', url, 14, projection);
},
// Load line in the visible area
loadLines: function(projection) {
let url = baseTileUrl + lineLayer + tileStyle;
loadTiles('line', url, 14, projection);
},
// Get visible sequences
sequences: function(projection) {
const viewport = projection.clipExtent();
const min = [viewport[0][0], viewport[1][1]];
const max = [viewport[1][0], viewport[0][1]];
const bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
const sequenceIds = {};
let lineStrings = [];
_mlyCache.images.rtree.search(bbox)
.forEach(function(d) {
if (d.data.sequence_id) {
sequenceIds[d.data.sequence_id] = true;
}
});
Object.keys(sequenceIds).forEach(function(sequenceId) {
if (_mlyCache.sequences.lineString[sequenceId]) {
lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceId]);
}
});
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;
const markers = context.container().selectAll('.layer-mapilio .viewfield-group');
const sequences = context.container().selectAll('.layer-mapilio .sequence');
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;
},
updateUrlImage: function(imageKey) {
if (!window.mocha) {
var hash = utilStringQs(window.location.hash);
if (imageKey) {
hash.photo = 'mapilio/' + imageKey;
} else {
delete hash.photo;
}
window.location.replace('#' + utilQsString(hash, true));
}
},
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-pnlm', options);
},
selectImage: function (context, id) {
let that = this;
let d = this.cachedImage(id);
this.setActiveImage(d);
this.updateUrlImage(d.id);
let viewer = context.container().select('.photoviewer');
if (!viewer.empty()) viewer.datum(d);
this.setStyles(context, null);
if (!d) return this;
let wrap = context.container().select('.photoviewer .mapilio-wrapper');
let attribution = wrap.selectAll('.photo-attribution').text('');
if (d.capture_time) {
attribution
.append('span')
.attr('class', 'captured_at')
.text(localeDateString(d.capture_time));
attribution
.append('span')
.text('|');
}
attribution
.append('a')
.attr('class', 'image-link')
.attr('target', '_blank')
.attr('href', `https://mapilio.com/app?lat=${d.loc[1]}&lng=${d.loc[0]}&zoom=17&pId=${d.id}`)
.text('mapilio.com');
wrap
.transition()
.duration(100)
.call(imgZoom.transform, d3_zoomIdentity);
wrap
.selectAll('img')
.remove();
getImageData(d.id,d.sequence_id).then(function () {
if (d.isPano) {
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);
}
}
} else {
// make non-panoramic photo viewer
that.initOnlyPhoto(context);
}
});
function localeDateString(s) {
if (!s) return null;
var options = { day: 'numeric', month: 'short', year: 'numeric' };
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString(localizer.localeCode(), options);
}
return this;
},
initOnlyPhoto: function (context) {
if (_pannellumViewer) {
_pannellumViewer.destroy();
_pannellumViewer = null;
}
let wrap = context.container().select('#ideditor-viewer-mapilio-simple');
let imgWrap = wrap.select('img');
if (!imgWrap.empty()) {
imgWrap.attr('src',_mlySceneOptions.panorama);
} else {
wrap.append('img')
.attr('src',_mlySceneOptions.panorama);
}
},
ensureViewerLoaded: function(context) {
let that = this;
let imgWrap = context.container().select('#ideditor-viewer-mapilio-simple > img');
if (!imgWrap.empty()) {
imgWrap.remove();
}
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)
.on('dblclick.zoom', null);
wrapEnter
.append('div')
.attr('class', 'photo-attribution fillD');
const controlsEnter = wrapEnter
.append('div')
.attr('class', 'photo-controls-wrap')
.append('div')
.attr('class', 'photo-controls-mapilio');
controlsEnter
.append('button')
.on('click.back', step(-1))
.text('◄');
controlsEnter
.append('button')
.on('click.forward', step(1))
.text('►');
wrapEnter
.append('div')
.attr('id', 'ideditor-viewer-mapilio-pnlm');
wrapEnter
.append('div')
.attr('id', 'ideditor-viewer-mapilio-simple-wrap')
.call(imgZoom.on('zoom', zoomPan))
.append('div')
.attr('id', 'ideditor-viewer-mapilio-simple');
// 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;
});
function step(stepBy) {
return function () {
if (!_mlyActiveImage) return;
const imageId = _mlyActiveImage.id;
const nextIndex = imageId + stepBy;
if (!nextIndex) return;
const nextImage = _mlyCache.images.forImageId[nextIndex];
context.map().centerEase(nextImage.loc);
that.selectImage(context, nextImage.id);
};
}
function zoomPan(d3_event) {
var t = d3_event.transform;
context.container().select('.photoviewer #ideditor-viewer-mapilio-simple')
.call(utilSetTransform, t.x, t.y, t.k);
}
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);
this.updateUrlImage(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() {
return _mlyCache;
}
};
+1
View File
@@ -28,3 +28,4 @@ export { svgTagPattern } from './tag_pattern.js';
export { svgTouch } from './touch.js';
export { svgTurns } from './turns.js';
export { svgVertices } from './vertices.js';
export { svgMapilioImages } from './mapilio_images.js';
+3 -1
View File
@@ -14,6 +14,7 @@ import { svgMapillaryPosition } from './mapillary_position';
import { svgMapillarySigns } from './mapillary_signs';
import { svgMapillaryMapFeatures } from './mapillary_map_features';
import { svgKartaviewImages } from './kartaview_images';
import { svgMapilioImages } from './mapilio_images';
import { svgOsm } from './osm';
import { svgNotes } from './notes';
import { svgTouch } from './touch';
@@ -37,10 +38,11 @@ export function svgLayers(projection, context) {
{ id: 'mapillary-map-features', layer: svgMapillaryMapFeatures(projection, context, dispatch) },
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },
{ id: 'kartaview', layer: svgKartaviewImages(projection, context, dispatch) },
{ id: 'mapilio', layer: svgMapilioImages(projection, context, dispatch) },
{ id: 'vegbilder', layer: svgVegbilder(projection, context, dispatch) },
{ id: 'debug', layer: svgDebug(projection, context, dispatch) },
{ id: 'geolocate', layer: svgGeolocate(projection, context, dispatch) },
{ id: 'touch', layer: svgTouch(projection, context, dispatch) }
{ id: 'touch', layer: svgTouch(projection, context, dispatch) },
];
+249
View File
@@ -0,0 +1,249 @@
import _throttle from 'lodash-es/throttle';
import { select as d3_select } from 'd3-selection';
import { services } from '../services';
import {svgPath, svgPointTransform} from './helpers';
export function svgMapilioImages(projection, context, dispatch) {
const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
const minZoom = 12;
let layer = d3_select(null);
let _mapilio;
const viewFieldZoomLevel = 18;
function init() {
if (svgMapilioImages.initialized) return;
svgMapilioImages.enabled = false;
svgMapilioImages.initialized = true;
}
function getService() {
if (services.mapilio && !_mapilio) {
_mapilio = services.mapilio;
_mapilio.event.on('loadedImages', throttledRedraw);
} else if (!services.mapilio && _mapilio) {
_mapilio = null;
}
return _mapilio;
}
function showLayer() {
const service = getService();
if (!service) return;
editOn();
layer
.style('opacity', 0)
.transition()
.duration(250)
.style('opacity', 1)
.on('end', function () { dispatch.call('change'); });
}
function hideLayer() {
throttledRedraw.cancel();
layer
.transition()
.duration(250)
.style('opacity', 0)
.on('end', editOff);
}
function transform(d) {
let t = svgPointTransform(projection)(d);
if (d.heading) {
t += ' rotate(' + Math.floor(d.heading) + ',0,0)';
}
return t;
}
function editOn() {
layer.style('display', 'block');
}
function editOff() {
layer.selectAll('.viewfield-group').remove();
layer.style('display', 'none');
}
function click(d3_event, image) {
const service = getService();
if (!service) return;
service
.ensureViewerLoaded(context, image.id)
.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) : []);
let images = (service ? service.images(projection) : []);
let traces = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences, function(d) { return d.properties.id; });
// exit
traces.exit()
.remove();
traces.enter()
.append('path')
.attr('class', 'sequence')
.merge(traces)
.attr('d', svgPath(projection).geojson);
const groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(images, function(d) { return d.id; });
// exit
groups.exit()
.remove();
// enter
const groupsEnter = groups.enter()
.append('g')
.attr('class', 'viewfield-group')
.on('mouseenter', mouseover)
.on('mouseleave', mouseout)
.on('click', click);
groupsEnter
.append('g')
.attr('class', 'viewfield-scale');
// update
const markers = groups
.merge(groupsEnter)
.sort(function(a, b) {
return b.loc[1] - a.loc[1]; // sort Y
})
.attr('transform', transform)
.select('.viewfield-scale');
markers.selectAll('circle')
.data([0])
.enter()
.append('circle')
.attr('dx', '0')
.attr('dy', '0')
.attr('r', '6');
const viewfields = markers.selectAll('.viewfield')
.data(showViewfields ? [0] : []);
viewfields.exit()
.remove();
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__.isPano) {
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';
}
}
}
function drawImages(selection) {
const enabled = svgMapilioImages.enabled;
const service = getService();
layer = selection.selectAll('.layer-mapilio')
.data(service ? [0] : []);
layer.exit()
.remove();
const layerEnter = layer.enter()
.append('g')
.attr('class', 'layer-mapilio')
.style('display', enabled ? 'block' : 'none');
layerEnter
.append('g')
.attr('class', 'sequences');
layerEnter
.append('g')
.attr('class', 'markers');
layer = layerEnter
.merge(layer);
if (enabled) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
service.loadImages(projection);
service.loadLines(projection);
} else {
editOff();
}
}
}
drawImages.enabled = function(_) {
if (!arguments.length) return svgMapilioImages.enabled;
svgMapilioImages.enabled = _;
if (svgMapilioImages.enabled) {
showLayer();
context.photos().on('change.mapilio_images', null);
} else {
hideLayer();
context.photos().on('change.mapilio_images', null);
}
dispatch.call('change');
return this;
};
drawImages.supported = function() {
return !!getService();
};
init();
return drawImages;
}
+5 -1
View File
@@ -204,7 +204,11 @@ export function uiField(context, presetField, entityIDs, options) {
referenceKey = referenceKey.replace(/:$/, '');
}
reference = uiTagReference(d.reference || { key: referenceKey }, context);
var referenceOptions = d.reference || {
key: referenceKey,
value: _tags[referenceKey]
};
reference = uiTagReference(referenceOptions, context);
if (_state === 'hover') {
reference.showing(false);
}
+1 -1
View File
@@ -483,7 +483,7 @@ export function uiFieldCombo(field, context) {
.attr('type', 'text')
.attr('id', field.domId)
.call(utilNoAuto)
.call(initCombo, selection)
.call(initCombo, _container)
.merge(_input);
if (_isSemi) {
+6
View File
@@ -1,3 +1,4 @@
import { marked } from 'marked';
import {
select as d3_select
} from 'd3-selection';
@@ -689,5 +690,10 @@ export function uiInit(context) {
_saveLoading = d3_select(null);
});
marked.use({
mangle: false,
headerIds: false,
});
return ui;
}
+1
View File
@@ -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); }
if (services.vegbilder) { services.vegbilder.hideViewer(context); }
})
.append('div')
+6 -3
View File
@@ -9878,9 +9878,10 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -16685,7 +16686,9 @@
}
},
"word-wrap": {
"version": "1.2.3",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wordwrap": {
+1
View File
@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bowl-rice" class="svg-inline--fa fa-bowl-rice" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M176 56c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H200c-13.3 0-24-10.7-24-24zm24 48h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H200c-13.3 0-24-10.7-24-24s10.7-24 24-24zM56 176H72c13.3 0 24 10.7 24 24s-10.7 24-24 24H56c-13.3 0-24-10.7-24-24s10.7-24 24-24zM0 283.4C0 268.3 12.3 256 27.4 256H484.6c15.1 0 27.4 12.3 27.4 27.4c0 70.5-44.4 130.7-106.7 154.1L403.5 452c-2 16-15.6 28-31.8 28H140.2c-16.1 0-29.8-12-31.8-28l-1.8-14.4C44.4 414.1 0 353.9 0 283.4zM224 200c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H248c-13.3 0-24-10.7-24-24zm-96 0c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H152c-13.3 0-24-10.7-24-24zm-24-96h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H104c-13.3 0-24-10.7-24-24s10.7-24 24-24zm216 96c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H344c-13.3 0-24-10.7-24-24zm-24-96h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H296c-13.3 0-24-10.7-24-24s10.7-24 24-24zm120 96c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H440c-13.3 0-24-10.7-24-24zm-24-96h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H392c-13.3 0-24-10.7-24-24s10.7-24 24-24zM296 32h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H296c-13.3 0-24-10.7-24-24s10.7-24 24-24z"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+1
View File
@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="building-flag" class="svg-inline--fa fa-building-flag" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M48 0C21.5 0 0 21.5 0 48V464c0 26.5 21.5 48 48 48h96V432c0-26.5 21.5-48 48-48s48 21.5 48 48v80h96c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H48zM64 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240zm112-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V240zM80 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V112zM272 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16zM448 0c-17.7 0-32 14.3-32 32V512h64V192H624c8.8 0 16-7.2 16-16V48c0-8.8-7.2-16-16-16H480c0-17.7-14.3-32-32-32z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="person-arrow-up-from-line" class="svg-inline--fa fa-person-arrow-up-from-line" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M192 96a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm-8 352V352h16v96H184zm-64 0H32c-17.7 0-32 14.3-32 32s14.3 32 32 32H152h80H608c17.7 0 32-14.3 32-32s-14.3-32-32-32H264V256.9l28.6 47.5c9.1 15.1 28.8 20 43.9 10.9s20-28.8 10.9-43.9l-58.3-97c-17.4-28.9-48.6-46.6-82.3-46.6H177.1c-33.7 0-64.9 17.7-82.3 46.6l-58.3 97c-9.1 15.1-4.2 34.8 10.9 43.9s34.8 4.2 43.9-10.9L120 256.9V448zM598.6 121.4l-80-80c-12.5-12.5-32.8-12.5-45.3 0l-80 80c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L464 141.3 464 384c0 17.7 14.3 32 32 32s32-14.3 32-32V141.3l25.4 25.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3z"></path></svg>

After

Width:  |  Height:  |  Size: 842 B

+1
View File
@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plate-wheat" class="svg-inline--fa fa-plate-wheat" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M176 32c44.2 0 80 35.8 80 80v16c0 8.8-7.2 16-16 16c-44.2 0-80-35.8-80-80V48c0-8.8 7.2-16 16-16zM56 64h48c13.3 0 24 10.7 24 24s-10.7 24-24 24H56c-13.3 0-24-10.7-24-24s10.7-24 24-24zM24 136H136c13.3 0 24 10.7 24 24s-10.7 24-24 24H24c-13.3 0-24-10.7-24-24s10.7-24 24-24zm8 96c0-13.3 10.7-24 24-24h48c13.3 0 24 10.7 24 24s-10.7 24-24 24H56c-13.3 0-24-10.7-24-24zM272 48c0-8.8 7.2-16 16-16c44.2 0 80 35.8 80 80v16c0 8.8-7.2 16-16 16c-44.2 0-80-35.8-80-80V48zM400 32c44.2 0 80 35.8 80 80v16c0 8.8-7.2 16-16 16c-44.2 0-80-35.8-80-80V48c0-8.8 7.2-16 16-16zm80 160v16c0 44.2-35.8 80-80 80c-8.8 0-16-7.2-16-16V256c0-44.2 35.8-80 80-80c8.8 0 16 7.2 16 16zM352 176c8.8 0 16 7.2 16 16v16c0 44.2-35.8 80-80 80c-8.8 0-16-7.2-16-16V256c0-44.2 35.8-80 80-80zm-96 16v16c0 44.2-35.8 80-80 80c-8.8 0-16-7.2-16-16V256c0-44.2 35.8-80 80-80c8.8 0 16 7.2 16 16zM3.5 347.6C1.6 332.9 13 320 27.8 320H484.2c14.8 0 26.2 12.9 24.4 27.6C502.3 397.8 464.2 437 416 446v2c0 17.7-14.3 32-32 32H128c-17.7 0-32-14.3-32-32v-2c-48.2-9-86.3-48.2-92.5-98.4z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plug-circle-bolt" class="svg-inline--fa fa-plug-circle-bolt" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M96 0C78.3 0 64 14.3 64 32v96h64V32c0-17.7-14.3-32-32-32zM288 0c-17.7 0-32 14.3-32 32v96h64V32c0-17.7-14.3-32-32-32zM32 160c-17.7 0-32 14.3-32 32s14.3 32 32 32v32c0 77.4 55 142 128 156.8V480c0 17.7 14.3 32 32 32s32-14.3 32-32V412.8c12.3-2.5 24.1-6.4 35.1-11.5c-2.1-10.8-3.1-21.9-3.1-33.3c0-80.3 53.8-148 127.3-169.2c.5-2.2 .7-4.5 .7-6.8c0-17.7-14.3-32-32-32H32zM432 512a144 144 0 1 0 0-288 144 144 0 1 0 0 288zm47.9-225c4.3 3.7 5.4 9.9 2.6 14.9L452.4 356H488c5.2 0 9.8 3.3 11.4 8.2s-.1 10.3-4.2 13.4l-96 72c-4.5 3.4-10.8 3.2-15.1-.6s-5.4-9.9-2.6-14.9L411.6 380H376c-5.2 0-9.8-3.3-11.4-8.2s.1-10.3 4.2-13.4l96-72c4.5-3.4 10.8-3.2 15.1 .6z"></path></svg>

After

Width:  |  Height:  |  Size: 881 B

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="triangle-exclamation" class="svg-inline--fa fa-triangle-exclamation" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"></path></svg>

After

Width:  |  Height:  |  Size: 555 B

+1
View File
@@ -39,6 +39,7 @@ describe('iD.svgLayers', function () {
expect(d3.select(nodes[9]).classed('mapillary-map-features')).to.be.true;
expect(d3.select(nodes[10]).classed('mapillary-signs')).to.be.true;
expect(d3.select(nodes[11]).classed('kartaview')).to.be.true;
expect(d3.select(nodes[12]).classed('mapilio')).to.be.true;
expect(d3.select(nodes[12]).classed('vegbilder')).to.be.true;
expect(d3.select(nodes[13]).classed('debug')).to.be.true;
expect(d3.select(nodes[14]).classed('geolocate')).to.be.true;