Fix bbox bug, add ids to filters, add filter state to the URL

This commit is contained in:
pietervdvn 2021-09-27 18:35:32 +02:00
parent 38037014b0
commit 0a9e7c0b36
23 changed files with 248 additions and 59 deletions

View file

@ -42,6 +42,8 @@ import {Tiles} from "./Models/TileRange";
import {TileHierarchyAggregator} from "./UI/ShowDataLayer/PerTileCountAggregator"; import {TileHierarchyAggregator} from "./UI/ShowDataLayer/PerTileCountAggregator";
import {BBox} from "./Logic/GeoOperations"; import {BBox} from "./Logic/GeoOperations";
import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource"; import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource";
import FilterConfig from "./Models/ThemeConfig/FilterConfig";
import FilteredLayer from "./Models/FilteredLayer";
export class InitUiElements { export class InitUiElements {
static InitAll( static InitAll(
@ -406,8 +408,10 @@ export class InitUiElements {
private static InitLayers(): void { private static InitLayers(): void {
const state = State.state; const state = State.state;
const empty = []
state.filteredLayers = state.layoutToUse.map((layoutToUse) => { state.filteredLayers = state.layoutToUse.map((layoutToUse) => {
const flayers = []; const flayers: FilteredLayer[] = [];
for (const layer of layoutToUse.layers) { for (const layer of layoutToUse.layers) {
const isDisplayed = QueryParameters.GetQueryParameter( const isDisplayed = QueryParameters.GetQueryParameter(
@ -422,23 +426,40 @@ export class InitUiElements {
const flayer = { const flayer = {
isDisplayed: isDisplayed, isDisplayed: isDisplayed,
layerDef: layer, layerDef: layer,
appliedFilters: new UIEventSource<TagsFilter>(undefined), appliedFilters: new UIEventSource<{ filter: FilterConfig, selected: number }[]>([]),
}; };
if (layer.filters.length > 0) {
const filtersPerName = new Map<string, FilterConfig>()
layer.filters.forEach(f => filtersPerName.set(f.id, f))
const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "","Filtering state for a layer")
flayer.appliedFilters.map(filters => {
filters = filters ?? []
return filters.map(f => f.filter.id + "." + f.selected).join(",")
}, [], textual => {
if(textual.length === 0){
return empty
}
return textual.split(",").map(part => {
const [filterId, selected] = part.split(".");
return {filter: filtersPerName.get(filterId), selected: Number(selected)}
}).filter(f => f.filter !== undefined && !isNaN(f.selected))
}).syncWith(qp, true)
}
flayers.push(flayer); flayers.push(flayer);
} }
return flayers; return flayers;
}); });
const layers = State.state.layoutToUse.data.layers
const clusterShow = Math.min(...layers.map(layer => layer.minzoom))
const layers = State.state.layoutToUse.data.layers
const clusterCounter = TileHierarchyAggregator.createHierarchy() const clusterCounter = TileHierarchyAggregator.createHierarchy()
new ShowDataLayer({ new ShowDataLayer({
features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.data.clustering.minNeededElements), features: clusterCounter.getCountsForZoom(State.state.locationControl, State.state.layoutToUse.data.clustering.minNeededElements),
leafletMap: State.state.leafletMap, leafletMap: State.state.leafletMap,
layerToShow: ShowTileInfo.styling, layerToShow: ShowTileInfo.styling,
doShowLayer: layers.length === 1 ? undefined : State.state.locationControl.map(l => l.zoom < clusterShow)
}) })
State.state.featurePipeline = new FeaturePipeline( State.state.featurePipeline = new FeaturePipeline(

View file

@ -5,13 +5,14 @@ import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import Hash from "../../Web/Hash"; import Hash from "../../Web/Hash";
import {BBox} from "../../GeoOperations"; import {BBox} from "../../GeoOperations";
export default class FilteringFeatureSource implements FeatureSourceForLayer , Tiled { export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = public features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([]); new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name; public readonly name;
public readonly layer: FilteredLayer; public readonly layer: FilteredLayer;
public readonly tileIndex : number public readonly tileIndex: number
public readonly bbox : BBox public readonly bbox: BBox
constructor( constructor(
state: { state: {
locationControl: UIEventSource<{ zoom: number }>, locationControl: UIEventSource<{ zoom: number }>,
@ -21,7 +22,7 @@ public readonly tileIndex : number
upstream: FeatureSourceForLayer upstream: FeatureSourceForLayer
) { ) {
const self = this; const self = this;
this.name = "FilteringFeatureSource("+upstream.name+")" this.name = "FilteringFeatureSource(" + upstream.name + ")"
this.tileIndex = tileIndex this.tileIndex = tileIndex
this.bbox = BBox.fromTileIndex(tileIndex) this.bbox = BBox.fromTileIndex(tileIndex)
@ -50,12 +51,15 @@ public readonly tileIndex : number
} }
const tagsFilter = layer.appliedFilters.data; const tagsFilter = layer.appliedFilters.data;
if (tagsFilter) { for (const filter of tagsFilter ?? []) {
if (!tagsFilter.matchesProperties(f.feature.properties)) { const neededTags = filter.filter.options[filter.selected].osmTags
if (!neededTags.matchesProperties(f.feature.properties)) {
// Hidden by the filter on the layer itself - we want to hide it no matter wat // Hidden by the filter on the layer itself - we want to hide it no matter wat
return false; return false;
} }
} }
if (!layer.isDisplayed) { if (!layer.isDisplayed) {
// The layer itself is either disabled or hidden due to zoom constraints // The layer itself is either disabled or hidden due to zoom constraints
// We should return true, but it might still match some other layer // We should return true, but it might still match some other layer
@ -80,7 +84,7 @@ public readonly tileIndex : number
}); });
layer.appliedFilters.addCallback(_ => { layer.appliedFilters.addCallback(_ => {
if(!layer.isDisplayed.data){ if (!layer.isDisplayed.data) {
// Currently not shown. // Currently not shown.
// Note that a change in 'isSHown' will trigger an update as well, so we don't have to watch it another time // Note that a change in 'isSHown' will trigger an update as well, so we don't have to watch it another time
return; return;

View file

@ -150,6 +150,7 @@ export default class MetaTagging {
for (const f of functions) { for (const f of functions) {
f(params, feature); f(params, feature);
} }
State.state.allElements.getEventSourceById(feature.properties.id).ping();
} catch (e) { } catch (e) {
console.error("While calculating a tag value: ", e) console.error("While calculating a tag value: ", e)
} }

View file

@ -2,7 +2,7 @@ import {Utils} from "../Utils";
export default class Constants { export default class Constants {
public static vNumber = "0.10.0-alpha-2"; public static vNumber = "0.10.0-alpha-3";
public static ImgurApiKey = '7070e7167f0a25a' public static ImgurApiKey = '7070e7167f0a25a'
// The user journey states thresholds when a new feature gets unlocked // The user journey states thresholds when a new feature gets unlocked

View file

@ -1,9 +1,10 @@
import {UIEventSource} from "../Logic/UIEventSource"; import {UIEventSource} from "../Logic/UIEventSource";
import LayerConfig from "./ThemeConfig/LayerConfig"; import LayerConfig from "./ThemeConfig/LayerConfig";
import {And} from "../Logic/Tags/And"; import {And} from "../Logic/Tags/And";
import FilterConfig from "./ThemeConfig/FilterConfig";
export default interface FilteredLayer { export default interface FilteredLayer {
readonly isDisplayed: UIEventSource<boolean>; readonly isDisplayed: UIEventSource<boolean>;
readonly appliedFilters: UIEventSource<And>; readonly appliedFilters: UIEventSource<{filter: FilterConfig, selected: number}[]>;
readonly layerDef: LayerConfig; readonly layerDef: LayerConfig;
} }

View file

@ -5,7 +5,8 @@ import Translations from "../../UI/i18n/Translations";
import {TagUtils} from "../../Logic/Tags/TagUtils"; import {TagUtils} from "../../Logic/Tags/TagUtils";
export default class FilterConfig { export default class FilterConfig {
readonly options: { public readonly id: string
public readonly options: {
question: Translation; question: Translation;
osmTags: TagsFilter; osmTags: TagsFilter;
}[]; }[];
@ -14,11 +15,18 @@ export default class FilterConfig {
if (json.options === undefined) { if (json.options === undefined) {
throw `A filter without options was given at ${context}` throw `A filter without options was given at ${context}`
} }
if (json.id === undefined) {
throw `A filter without id was found at ${context}`
}
if(json.id.match(/^[a-zA-Z0-9_-]*$/) === null){
throw `A filter with invalid id was found at ${context}. Ids should only contain letters, numbers or - _`
}
if (json.options.map === undefined) { if (json.options.map === undefined) {
throw `A filter was given where the options aren't a list at ${context}` throw `A filter was given where the options aren't a list at ${context}`
} }
this.id = json.id;
this.options = json.options.map((option, i) => { this.options = json.options.map((option, i) => {
const question = Translations.T( const question = Translations.T(
option.question, option.question,

View file

@ -1,6 +1,10 @@
import {AndOrTagConfigJson} from "./TagConfigJson"; import {AndOrTagConfigJson} from "./TagConfigJson";
export default interface FilterConfigJson { export default interface FilterConfigJson {
/**
* An id/name for this filter, used to set the URL parameters
*/
id: string,
/** /**
* The options for a filter * The options for a filter
* If there are multiple options these will be a list of radio buttons * If there are multiple options these will be a list of radio buttons

View file

@ -7,8 +7,6 @@ import Combine from "../Base/Combine";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import {Translation} from "../i18n/Translation"; import {Translation} from "../i18n/Translation";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {And} from "../../Logic/Tags/And";
import {UIEventSource} from "../../Logic/UIEventSource"; import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement";
import State from "../../State"; import State from "../../State";
@ -16,11 +14,6 @@ import FilteredLayer from "../../Models/FilteredLayer";
import BackgroundSelector from "./BackgroundSelector"; import BackgroundSelector from "./BackgroundSelector";
import FilterConfig from "../../Models/ThemeConfig/FilterConfig"; import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
/**
* Shows the filter
*/
export default class FilterView extends VariableUiElement { export default class FilterView extends VariableUiElement {
constructor(filteredLayer: UIEventSource<FilteredLayer[]>) { constructor(filteredLayer: UIEventSource<FilteredLayer[]>) {
const backgroundSelector = new Toggle( const backgroundSelector = new Toggle(
@ -101,26 +94,52 @@ export default class FilterView extends VariableUiElement {
return undefined; return undefined;
} }
let listFilterElements: [BaseUIElement, UIEventSource<TagsFilter>][] = layer.filters.map( const filterIndexes = new Map<string, number>()
layer.filters.forEach((f, i) => filterIndexes.set(f.id, i))
let listFilterElements: [BaseUIElement, UIEventSource<{ filter: FilterConfig, selected: number }>][] = layer.filters.map(
FilterView.createFilter FilterView.createFilter
); );
const update = () => { listFilterElements.forEach((inputElement, i) =>
let listTagsFilters = Utils.NoNull( inputElement[1].addCallback((changed) => {
listFilterElements.map((input) => input[1].data) const oldValue = flayer.appliedFilters.data
);
flayer.appliedFilters.setData(new And(listTagsFilters));
};
listFilterElements.forEach((inputElement) => if(changed === undefined){
inputElement[1].addCallback((_) => update()) // Lets figure out which filter should be removed
// We know this inputElement corresponds with layer.filters[i]
// SO, if there is a value in 'oldValue' with this filter, we have to recalculated
if(!oldValue.some(f => f.filter === layer.filters[i])){
// The filter to remove is already gone, we can stop
return;
}
}else if(oldValue.some(f => f.filter === changed.filter && f.selected === changed.selected)){
// The changed value is already there
return;
}
const listTagsFilters = Utils.NoNull(
listFilterElements.map((input) => input[1].data)
);
console.log(listTagsFilters, oldValue)
flayer.appliedFilters.setData(listTagsFilters);
})
); );
flayer.appliedFilters.addCallbackAndRun(appliedFilters => { flayer.appliedFilters.addCallbackAndRun(appliedFilters => {
if (appliedFilters === undefined || appliedFilters.and.length === 0) { for (let i = 0; i < layer.filters.length; i++){
listFilterElements.forEach(filter => filter[1].setData(undefined)) const filter = layer.filters[i];
return let foundMatch = undefined
for (const appliedFilter of appliedFilters) {
if(appliedFilter.filter === filter){
foundMatch = appliedFilter
break;
}
}
listFilterElements[i][1].setData(foundMatch)
} }
}) })
return new Combine(listFilterElements.map(input => input[0].SetClass("mt-3"))) return new Combine(listFilterElements.map(input => input[0].SetClass("mt-3")))
@ -128,7 +147,7 @@ export default class FilterView extends VariableUiElement {
} }
private static createFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<TagsFilter>] { private static createFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<{ filter: FilterConfig, selected: number }>] {
if (filterConfig.options.length === 1) { if (filterConfig.options.length === 1) {
let option = filterConfig.options[0]; let option = filterConfig.options[0];
@ -142,20 +161,36 @@ export default class FilterView extends VariableUiElement {
.ToggleOnClick() .ToggleOnClick()
.SetClass("block m-1") .SetClass("block m-1")
return [toggle, toggle.isEnabled.map(enabled => enabled ? option.osmTags : undefined, [], tags => tags !== undefined)] const selected = {
filter: filterConfig,
selected: 0
}
return [toggle, toggle.isEnabled.map(enabled => enabled ? selected : undefined, [],
f => f?.filter === filterConfig && f?.selected === 0)
]
} }
let options = filterConfig.options; let options = filterConfig.options;
const values = options.map((f, i) => ({
filter: filterConfig, selected: i
}))
const radio = new RadioButton( const radio = new RadioButton(
options.map( options.map(
(option) => (option, i) =>
new FixedInputElement(option.question.Clone(), option.osmTags) new FixedInputElement(option.question.Clone(), i)
), ),
{ {
dontStyle: true dontStyle: true
} }
); );
return [radio, radio.GetValue()] return [radio,
radio.GetValue().map(
i => values[i],
[],
selected => {
return selected?.selected
}
)]
} }
} }

View file

@ -223,14 +223,14 @@ export default class SimpleAddUI extends Toggle {
] ]
).SetClass("flex flex-col") ).SetClass("flex flex-col")
).onClick(() => { ).onClick(() => {
preset.layerToAddTo.appliedFilters.setData(new And([])) preset.layerToAddTo.appliedFilters.setData([])
cancel() cancel()
}) })
const disableFiltersOrConfirm = new Toggle( const disableFiltersOrConfirm = new Toggle(
openLayerOrConfirm, openLayerOrConfirm,
disableFilter, disableFilter,
preset.layerToAddTo.appliedFilters.map(filters => filters === undefined || filters.normalize().and.length === 0) preset.layerToAddTo.appliedFilters.map(filters => filters === undefined || filters.length === 0)
) )

View file

@ -258,6 +258,7 @@
"wayHandling": 1, "wayHandling": 1,
"filter": [ "filter": [
{ {
"id": "wheelchair",
"options": [ "options": [
{ {
"question": { "question": {
@ -275,6 +276,7 @@
] ]
}, },
{ {
"id": "shelter",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -170,6 +170,7 @@
], ],
"filter": [ "filter": [
{ {
"id": "opened-now",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -2627,6 +2627,7 @@
"wayHandling": 1, "wayHandling": 1,
"filter": [ "filter": [
{ {
"id": "vehicle-type",
"options": [ "options": [
{ {
"question": { "question": {
@ -2656,6 +2657,7 @@
] ]
}, },
{ {
"id": "working",
"options": [ "options": [
{ {
"question": { "question": {
@ -2671,6 +2673,7 @@
] ]
}, },
{ {
"id": "connection_type",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -648,6 +648,7 @@
"wayHandling": 1, "wayHandling": 1,
"filter": [ "filter": [
{ {
"id": "vehicle-type",
"options": [ "options": [
{ {
"question": { "question": {
@ -677,6 +678,7 @@
] ]
}, },
{ {
"id": "working",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -242,6 +242,7 @@ function run(file, protojson) {
}) })
proto["filter"].push({ proto["filter"].push({
id:"connection_type",
options: filterOptions options: filterOptions
}) })

View file

@ -560,6 +560,7 @@
], ],
"filter": [ "filter": [
{ {
"id": "opened-now",
"options": [ "options": [
{ {
"question": { "question": {
@ -571,6 +572,7 @@
] ]
}, },
{ {
"id": "vegetarian",
"options": [ "options": [
{ {
"question": { "question": {
@ -589,6 +591,7 @@
] ]
}, },
{ {
"id": "vegan",
"options": [ "options": [
{ {
"question": { "question": {
@ -605,6 +608,7 @@
] ]
}, },
{ {
"id": "halal",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -423,6 +423,7 @@
], ],
"filter": [ "filter": [
{ {
"id": "access",
"options": [ "options": [
{ {
"question": { "question": {
@ -433,6 +434,7 @@
] ]
}, },
{ {
"id": "dogs",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -444,6 +444,7 @@
}, },
"filter": [ "filter": [
{ {
"id": "kid-books",
"options": [ "options": [
{ {
"question": "Kinderboeken aanwezig?", "question": "Kinderboeken aanwezig?",
@ -452,6 +453,7 @@
] ]
}, },
{ {
"id": "adult-books",
"options": [ "options": [
{ {
"question": "Boeken voor volwassenen aanwezig?", "question": "Boeken voor volwassenen aanwezig?",
@ -460,6 +462,7 @@
] ]
}, },
{ {
"id": "inside",
"options": [ "options": [
{ {
"question": "Binnen of buiten", "question": "Binnen of buiten",

View file

@ -411,6 +411,7 @@
], ],
"filter": [ "filter": [
{ {
"id": "wheelchair",
"options": [ "options": [
{ {
"question": { "question": {
@ -421,6 +422,7 @@
] ]
}, },
{ {
"id": "changing_table",
"options": [ "options": [
{ {
"question": { "question": {
@ -431,6 +433,7 @@
] ]
}, },
{ {
"id": "free",
"options": [ "options": [
{ {
"question": { "question": {

View file

@ -143,6 +143,7 @@
}, },
"filter": [ "filter": [
{ {
"id": "name-alt",
"options": [ "options": [
{ {
"question": "Name contains 'alt'", "question": "Name contains 'alt'",
@ -151,6 +152,7 @@
] ]
}, },
{ {
"id": "name-wenslijn",
"options": [ "options": [
{ {
"question": "Name contains 'wenslijn'", "question": "Name contains 'wenslijn'",
@ -159,6 +161,7 @@
] ]
}, },
{ {
"id": "name-omleiding",
"options": [ "options": [
{ {
"question": "Name contains 'omleiding'", "question": "Name contains 'omleiding'",
@ -167,6 +170,7 @@
] ]
}, },
{ {
"id":"ref-alt",
"options": [ "options": [
{ {
"question": "Reference contains 'alt'", "question": "Reference contains 'alt'",
@ -175,6 +179,7 @@
] ]
}, },
{ {
"id": "missing_link",
"options": [ "options": [
{ {
"question": "No filter" "question": "No filter"
@ -194,6 +199,7 @@
] ]
}, },
{ {
"id": "proposed",
"options": [ "options": [
{ {
"question": "No filter" "question": "No filter"

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
viewBox="0 0 87.992996 87.883003"
id="svg12"
sodipodi:docname="housenumber_unknown_small.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
width="87.992996"
height="87.883003">
<metadata
id="metadata18">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs16" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview14"
showgrid="false"
inkscape:zoom="7.375"
inkscape:cx="-1.3561062"
inkscape:cy="19.621117"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<path
d="m 42.372766,41.559418 h 3.247468 c 0.421762,0 0.76133,0.339541 0.76133,0.761329 v 3.241506 c 0,0.421761 -0.339541,0.761329 -0.76133,0.761329 h -3.247468 c -0.421762,0 -0.76133,-0.339541 -0.76133,-0.761329 v -3.241506 c 0,-0.421761 0.339541,-0.761329 0.76133,-0.761329 z"
style="fill:#495aad;stroke-width:0.0542103;paint-order:normal"
id="path6"
inkscape:connector-curvature="0" />
<path
d="m 42.085614,42.793949 v 2.289464 c 0.381581,0 0.763173,0.381581 0.763173,0.763173 h 2.289463 c 0,-0.381581 0.381581,-0.763173 0.763173,-0.763173 v -2.289464 c -0.381581,0 -0.763173,-0.381581 -0.763173,-0.763172 h -2.289463 c 0,0.38158 -0.381581,0.763172 -0.763173,0.763172 z"
id="path8"
inkscape:connector-curvature="0"
style="fill:none;stroke:#ffffff;stroke-width:0.27187553" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -39,5 +39,13 @@
"https://github.com/streetcomplete/StreetComplete/tree/master/res/graphics", "https://github.com/streetcomplete/StreetComplete/tree/master/res/graphics",
"https://f-droid.org/packages/de.westnordost.streetcomplete/" "https://f-droid.org/packages/de.westnordost.streetcomplete/"
] ]
},
{
"path": "housenumber_unknown_small.svg",
"license": "CC0",
"authors": [
"Pieter Vander Vennet"
],
"sources": []
} }
] ]

View file

@ -15,11 +15,15 @@
"maintainer": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett", "maintainer": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett",
"icon": "./assets/themes/uk_addresses/housenumber_unknown.svg", "icon": "./assets/themes/uk_addresses/housenumber_unknown.svg",
"version": "2021-09-17", "version": "2021-09-17",
"startLat": -0.08528530407, "startLat": -0.08706,
"startLon": 51.52103754846, "startLon": 51.52224,
"startZoom": 18, "startZoom": 17,
"widenFactor": 1.5, "widenFactor": 1.01,
"socialImage": "", "socialImage": "",
"clustering": {
"minNeededFeatures": 25,
"maxZoom": 17
},
"layers": [ "layers": [
{ {
"id": "to_import", "id": "to_import",
@ -34,21 +38,21 @@
"minzoom": 12, "minzoom": 12,
"wayHandling": 1, "wayHandling": 1,
"icon": { "icon": {
"render": "./assets/themes/uk_addresses/housenumber_unknown.svg" "render": "./assets/themes/uk_addresses/housenumber_unknown.svg",
},
"iconSize": {
"render": "40,40,center",
"mappings": [ "mappings": [
{ {
"if": "_embedding_object:id~*", "if": "_embedding_object:id~*",
"then": "15,15,center" "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg"
}, },
{ {
"if": "_imported=yes", "if": "_imported=yes",
"then": "8,8,center" "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg"
} }
] ]
}, },
"iconSize": {
"render": "40,40,center"
},
"title": { "title": {
"render": "Address to be determined" "render": "Address to be determined"
}, },
@ -73,6 +77,22 @@
"_embedding_object:addr:housenumber=JSON.parse(feat.properties._embedding_object)?.['addr:housenumber']", "_embedding_object:addr:housenumber=JSON.parse(feat.properties._embedding_object)?.['addr:housenumber']",
"_embedding_object:addr:street=JSON.parse(feat.properties._embedding_object)?.['addr:street']", "_embedding_object:addr:street=JSON.parse(feat.properties._embedding_object)?.['addr:street']",
"_embedding_object:id=JSON.parse(feat.properties._embedding_object)?.id" "_embedding_object:id=JSON.parse(feat.properties._embedding_object)?.id"
],
"filter": [
{
"id": "to_handle",
"options": [
{
"question": "Only show non-matched objects",
"osmTags": {
"and": [
"_imported=",
"_embedding_object:id="
]
}
}
]
}
] ]
}, },
{ {

View file

@ -181,10 +181,10 @@ export default class GeoOperationsSpec extends T {
["bbox bounds test", ["bbox bounds test",
() => { () => {
const bbox = BBox.fromTile(16, 32754, 21785) const bbox = BBox.fromTile(16, 32754, 21785)
equal(-0.0714111328125, bbox.minLon) equal(-0.076904296875, bbox.minLon)
equal(-0.076904296875, bbox.maxLon) equal(-0.0714111328125, bbox.maxLon)
equal(51.53266860674158, bbox.minLat) equal(51.5292513551899, bbox.minLat)
equal(51.5292513551899, bbox.maxLat) equal(51.53266860674158, bbox.maxLat)
} }
] ]
] ]