Merge branch 'develop' into panoramax

This commit is contained in:
Martin Raifer
2024-07-29 15:13:38 +02:00
194 changed files with 837 additions and 560 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ const _listeners = {};
//
/**
* @param {string} k
* @param {string?} v
* @param {string?} [v]
* @returns {boolean} true if the action succeeded
*/
function corePreferences(k, v) {
+2 -2
View File
@@ -76,8 +76,8 @@ export function coreTree(head) {
});
head.parentRelations(entity).forEach(function(relation) {
if (memo[entity.id]) return;
memo[entity.id] = true;
if (memo[relation.id]) return;
memo[relation.id] = true;
if (_bboxes[relation.id]) {
removeEntity(relation);
insertions[relation.id] = relation;
+4 -2
View File
@@ -58,7 +58,8 @@ export var osmAreaKeysExceptions = {
station: true,
traverser: true,
turntable: true,
wash: true
wash: true,
ventilation_shaft: true
},
waterway: {
dam: true
@@ -167,6 +168,7 @@ export var osmOneWayTags = {
'ditch': true,
'drain': true,
'fish_pass': true,
'flowline': true,
'pressurised': true,
'river': true,
'spillway': true,
@@ -245,7 +247,7 @@ export var osmRailwayTrackTagValues = {
// "waterway" tag values for line features representing water flow
export var osmFlowingWaterwayTagValues = {
canal: true, ditch: true, drain: true, fish_pass: true, river: true, stream: true, tidal_channel: true
canal: true, ditch: true, drain: true, fish_pass: true, flowline: true, river: true, stream: true, tidal_channel: true
};
// Tags which values should be considered case sensitive when offering tag suggestions
+13 -6
View File
@@ -71,10 +71,17 @@ export function rendererFeatures(context) {
}
/**
* @callback FilterFunction
* @param {Record<string, string>} tags
* @param {string} [geometry]
* @returns {boolean}
*/
/**
* @param {string} k
* @param {(tags: Record<string, string>, geometry: string) => boolean} filter
* @param {?number} max
* @param {FilterFunction} filter
* @param {number} [max]
*/
function defineRule(k, filter, max) {
var isEnabled = true;
@@ -124,11 +131,11 @@ export function rendererFeatures(context) {
}, 250);
defineRule('building_parts', function isBuildingPart(tags) {
return tags['building:part'];
return !!tags['building:part'];
});
defineRule('indoor', function isIndoor(tags) {
return tags.indoor;
return !!tags.indoor;
});
defineRule('landuse', function isLanduse(tags, geometry) {
@@ -194,7 +201,7 @@ export function rendererFeatures(context) {
});
defineRule('aerialways', function isAerialways(tags) {
return tags.aerialway &&
return !!tags?.aerialway &&
tags.aerialway !== 'yes' &&
tags.aerialway !== 'station';
});
@@ -260,7 +267,7 @@ export function rendererFeatures(context) {
if (!arguments.length) {
return _keys.filter(function(k) { return _rules[k].hidden(); });
}
return _rules[k] && _rules[k].hidden();
return _rules[k]?.hidden();
};
+11
View File
@@ -30,6 +30,7 @@ let _mlyShowFeatureDetections = false;
let _mlyShowSignDetections = false;
let _mlyViewer;
let _mlyViewerFilter = ['all'];
let _isViewerOpen = false;
// Load all data for the specified type from Mapillary vector tiles
@@ -478,6 +479,8 @@ export default {
_mlyViewer.resize();
}
_isViewerOpen = true;
return this;
},
@@ -504,10 +507,18 @@ export default {
dispatch.call('loadedMapFeatures');
dispatch.call('loadedSigns');
_isViewerOpen = false;
return this.setStyles(context, null);
},
// Get viewer status
isViewerOpen: function() {
return _isViewerOpen;
},
// Update the URL with current image id
updateUrlImage: function(imageId) {
if (!window.mocha) {
+2 -2
View File
@@ -6,7 +6,7 @@ import stringify from 'fast-json-stable-stringify';
import polygonClipping from 'polygon-clipping';
import Protobuf from 'pbf';
import vt from '@mapbox/vector-tile';
import { VectorTile } from '@mapbox/vector-tile';
import { utilHashcode, utilRebind, utilTiler } from '../util';
@@ -22,7 +22,7 @@ function abortRequest(controller) {
function vtToGeoJSON(data, tile, mergeCache) {
var vectorTile = new vt.VectorTile(new Protobuf(data));
var vectorTile = new VectorTile(new Protobuf(data));
var layers = Object.keys(vectorTile.layers);
if (!Array.isArray(layers)) { layers = [layers]; }
+10
View File
@@ -8,6 +8,7 @@ import { uiTooltip } from './tooltip';
import { geoExtent } from '../geo/extent';
import { uiFieldHelp } from './field_help';
import { uiFields } from './fields';
import { LANGUAGE_SUFFIX_REGEX } from './fields/localized';
import { uiTagReference } from './tag_reference';
import { utilRebind, utilUniqueDomId } from '../util';
@@ -97,6 +98,15 @@ export function uiField(context, presetField, entityIDs, options) {
}
return false;
}
if (field.type === 'localized') {
for (let tagKey in _tags) {
// matches for field:<code>, where <code> is a BCP 47 locale code
let match = tagKey.match(LANGUAGE_SUFFIX_REGEX);
if (match && match[1] === field.key && match[2]) {
return true;
}
}
}
return _tags[key] !== undefined;
});
}
+2 -1
View File
@@ -14,6 +14,7 @@ import { uiLengthIndicator } from '../length_indicator';
var _languagesArray = [];
export const LANGUAGE_SUFFIX_REGEX = /^(.*):([a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2})?)$/;
export function uiFieldLocalized(field, context) {
var dispatch = d3_dispatch('change', 'input');
@@ -127,7 +128,7 @@ export function uiFieldLocalized(field, context) {
// matches for field:<code>, where <code> is a BCP 47 locale code
// motivation is to avoid matching on similarly formatted tags that are
// not for languages, e.g. name:left, name:source, etc.
var m = k.match(/^(.*):([a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-[A-Z]{2})?)$/);
var m = k.match(LANGUAGE_SUFFIX_REGEX);
if (m && m[1] === field.key && m[2]) {
var item = { lang: m[2], value: tags[k] };
if (existingLangs.has(item.lang)) {
+127 -1
View File
@@ -6,8 +6,11 @@ import { t } from '../core/localizer';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { svgIcon } from '../svg/icon';
import { utilGetDimensions } from '../util/dimensions';
import { utilRebind } from '../util';
import { utilRebind, utilStringQs } from '../util';
import { services } from '../services';
import { uiTooltip } from './tooltip';
import { actionChangeTags } from '../actions';
import { geoSphericalDistance } from '../geo';
export function uiPhotoviewer(context) {
@@ -62,6 +65,129 @@ export function uiPhotoviewer(context) {
buildResizeListener(selection, 'resize', dispatch, { resizeOnY: true })
);
// update sett_photo_from_viewer button on selection change and when tags change
context.features().on('change.setPhotoFromViewer', function() {
setPhotoFromViewerButton();
});
context.history().on('change.setPhotoFromViewer', function() {
setPhotoFromViewerButton();
});
function setPhotoFromViewerButton() {
if (services.mapillary.isViewerOpen()) {
if (context.mode().id !== 'select' || !(layerStatus('mapillary') && getServiceId() === 'mapillary')) {
buttonRemove();
} else {
if (selection.select('.set-photo-from-viewer').empty()) {
const button = buttonCreate();
button.on('click', function (e) {
e.preventDefault();
e.stopPropagation();
setMapillaryPhotoId();
buttonDisable('already_set');
});
}
buttonShowHide();
}
function setMapillaryPhotoId() {
const service = services.mapillary;
const image = service.getActiveImage();
const action = graph =>
context.selectedIDs().reduce((graph, entityID) => {
const tags = graph.entity(entityID).tags;
const action = actionChangeTags(entityID, {...tags, mapillary: image.id});
return action(graph);
}, graph);
const annotation = t('operations.change_tags.annotation');
context.perform(action, annotation);
}
}
function layerStatus(which) {
const layers = context.layers();
const layer = layers.layer(which);
return layer.enabled();
}
function getServiceId() {
const hash = utilStringQs(window.location.hash);
let serviceId;
if (hash.photo) {
let result = hash.photo.split('/');
serviceId = result[0];
}
return serviceId;
}
function buttonCreate() {
const button = selection.selectAll('.set-photo-from-viewer').data([0]);
const buttonEnter = button.enter()
.append('button')
.attr('class', 'set-photo-from-viewer')
.call(svgIcon('#iD-icon-plus'))
.call(uiTooltip()
.title(() => t.append('inspector.set_photo_from_viewer'))
.placement('right')
);
buttonEnter.select('.tooltip')
.classed('dark', true)
.style('width', '300px');
return buttonEnter;
}
function buttonRemove() {
const button = selection.selectAll('.set-photo-from-viewer').data([0]);
button.remove();
}
function buttonShowHide() {
const activeImage = services.mapillary.getActiveImage();
const graph = context.graph();
const entities = context.selectedIDs()
.map(id => graph.entity(id));
if (entities.map(entity => entity.tags.mapillary)
.every(value => value === activeImage?.id)) {
buttonDisable('already_set');
} else if (activeImage && entities.map(entity => entity.extent().center())
.every(loc => geoSphericalDistance(loc, activeImage.loc) > 100)) {
buttonDisable('too_far');
} else {
buttonDisable(false);
}
}
function buttonDisable(reason) {
const disabled = reason !== false;
const button = selection.selectAll('.set-photo-from-viewer').data([0]);
button.attr('disabled', disabled ? 'true' : null);
button.classed('disabled', disabled);
button.call(uiTooltip().destroyAny);
if (disabled) {
button.call(uiTooltip()
.title(() => t.append(`inspector.set_photo_from_viewer.disable.${reason}`))
.placement('right')
);
} else {
button.call(uiTooltip()
.title(() => t.append('inspector.set_photo_from_viewer.enable'))
.placement('right')
);
}
button.select('.tooltip')
.classed('dark', true)
.style('width', '300px');
}
}
function buildResizeListener(target, eventName, dispatch, options) {
var resizeOnX = !!options.resizeOnX;