Add 'steal' as special rendering, update 'multi', add entrance overview to onwheels layer
This commit is contained in:
parent
181c5583d2
commit
7e32413113
11 changed files with 462 additions and 73 deletions
|
@ -13,9 +13,9 @@ export interface ExtraFuncParams {
|
||||||
* Note that more features then requested can be given back.
|
* Note that more features then requested can be given back.
|
||||||
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
|
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
|
||||||
*/
|
*/
|
||||||
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, {id: string}>[][],
|
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, { id: string }>[][],
|
||||||
memberships: RelationsTracker
|
memberships: RelationsTracker
|
||||||
getFeatureById: (id: string) => Feature<Geometry, {id: string}>
|
getFeatureById: (id: string) => Feature<Geometry, { id: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,10 +31,11 @@ interface ExtraFunction {
|
||||||
|
|
||||||
class EnclosingFunc implements ExtraFunction {
|
class EnclosingFunc implements ExtraFunction {
|
||||||
_name = "enclosingFeatures"
|
_name = "enclosingFeatures"
|
||||||
_doc = ["Gives a list of all features in the specified layers which fully contain this object. Returned features will always be (multi)polygons. (LineStrings and Points from the other layers are ignored)","",
|
_doc = ["Gives a list of all features in the specified layers which fully contain this object. Returned features will always be (multi)polygons. (LineStrings and Points from the other layers are ignored)", "",
|
||||||
"The result is a list of features: `{feat: Polygon}[]`",
|
"The result is a list of features: `{feat: Polygon}[]`",
|
||||||
"This function will never return the feature itself."].join("\n")
|
"This function will never return the feature itself."].join("\n")
|
||||||
_args = ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"]
|
_args = ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"]
|
||||||
|
|
||||||
_f(params: ExtraFuncParams, feat: Feature<Geometry, any>) {
|
_f(params: ExtraFuncParams, feat: Feature<Geometry, any>) {
|
||||||
return (...layerIds: string[]) => {
|
return (...layerIds: string[]) => {
|
||||||
const result: { feat: any }[] = []
|
const result: { feat: any }[] = []
|
||||||
|
@ -51,14 +52,14 @@ class EnclosingFunc implements ExtraFunction {
|
||||||
}
|
}
|
||||||
for (const otherFeatures of otherFeaturess) {
|
for (const otherFeatures of otherFeaturess) {
|
||||||
for (const otherFeature of otherFeatures) {
|
for (const otherFeature of otherFeatures) {
|
||||||
if(seenIds.has(otherFeature.properties.id)){
|
if (seenIds.has(otherFeature.properties.id)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenIds.add(otherFeature.properties.id)
|
seenIds.add(otherFeature.properties.id)
|
||||||
if(otherFeature.geometry.type !== "Polygon" && otherFeature.geometry.type !== "MultiPolygon"){
|
if (otherFeature.geometry.type !== "Polygon" && otherFeature.geometry.type !== "MultiPolygon") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(GeoOperations.completelyWithin(feat, <Feature<Polygon | MultiPolygon, any>> otherFeature)){
|
if (GeoOperations.completelyWithin(feat, <Feature<Polygon | MultiPolygon, any>>otherFeature)) {
|
||||||
result.push({feat: otherFeature})
|
result.push({feat: otherFeature})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,10 +76,10 @@ class OverlapFunc implements ExtraFunction {
|
||||||
|
|
||||||
_name = "overlapWith";
|
_name = "overlapWith";
|
||||||
_doc = ["Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.",
|
_doc = ["Gives a list of features from the specified layer which this feature (partly) overlaps with. A point which is embedded in the feature is detected as well.",
|
||||||
"If the current feature is a point, all features that this point is embeded in are given." ,
|
"If the current feature is a point, all features that this point is embeded in are given.",
|
||||||
"",
|
"",
|
||||||
"The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point." ,
|
"The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point.",
|
||||||
"The resulting list is sorted in descending order by overlap. The feature with the most overlap will thus be the first in the list." ,
|
"The resulting list is sorted in descending order by overlap. The feature with the most overlap will thus be the first in the list.",
|
||||||
"",
|
"",
|
||||||
"For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')`",
|
"For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')`",
|
||||||
"",
|
"",
|
||||||
|
@ -89,6 +90,7 @@ class OverlapFunc implements ExtraFunction {
|
||||||
_f(params, feat) {
|
_f(params, feat) {
|
||||||
return (...layerIds: string[]) => {
|
return (...layerIds: string[]) => {
|
||||||
const result: { feat: any, overlap: number }[] = []
|
const result: { feat: any, overlap: number }[] = []
|
||||||
|
const seenIds = new Set<string>()
|
||||||
const bbox = BBox.get(feat)
|
const bbox = BBox.get(feat)
|
||||||
for (const layerId of layerIds) {
|
for (const layerId of layerIds) {
|
||||||
const otherFeaturess = params.getFeaturesWithin(layerId, bbox)
|
const otherFeaturess = params.getFeaturesWithin(layerId, bbox)
|
||||||
|
@ -99,12 +101,18 @@ class OverlapFunc implements ExtraFunction {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const otherFeatures of otherFeaturess) {
|
for (const otherFeatures of otherFeaturess) {
|
||||||
result.push(...GeoOperations.calculateOverlap(feat, otherFeatures));
|
const overlap = GeoOperations.calculateOverlap(feat, otherFeatures)
|
||||||
|
for (const overlappingFeature of overlap) {
|
||||||
|
if(seenIds.has(overlappingFeature.feat.properties.id)){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenIds.add(overlappingFeature.feat.properties.id)
|
||||||
|
result.push(overlappingFeature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.sort((a, b) => b.overlap - a.overlap)
|
result.sort((a, b) => b.overlap - a.overlap)
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export default class MetaTagging {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Recalculating metatags...")
|
||||||
const metatagsToApply: SimpleMetaTagger[] = []
|
const metatagsToApply: SimpleMetaTagger[] = []
|
||||||
for (const metatag of SimpleMetaTaggers.metatags) {
|
for (const metatag of SimpleMetaTaggers.metatags) {
|
||||||
if (metatag.includesDates) {
|
if (metatag.includesDates) {
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default class CreateNewNodeAction extends OsmCreateAction {
|
||||||
|
|
||||||
|
|
||||||
// Project the point onto the way
|
// Project the point onto the way
|
||||||
|
console.log("Snapping a node onto an existing way...")
|
||||||
const geojson = this._snapOnto.asGeoJson()
|
const geojson = this._snapOnto.asGeoJson()
|
||||||
const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat])
|
const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat])
|
||||||
const projectedCoor= <[number, number]>projected.geometry.coordinates
|
const projectedCoor= <[number, number]>projected.geometry.coordinates
|
||||||
|
|
|
@ -219,6 +219,9 @@ export abstract class OsmObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses the list of polygon features to determine if the given tags are a polygon or not.
|
* Uses the list of polygon features to determine if the given tags are a polygon or not.
|
||||||
|
*
|
||||||
|
* OsmObject.isPolygon({"building":"yes"}) // => true
|
||||||
|
* OsmObject.isPolygon({"highway":"residential"}) // => false
|
||||||
* */
|
* */
|
||||||
protected static isPolygon(tags: any): boolean {
|
protected static isPolygon(tags: any): boolean {
|
||||||
for (const tagsKey in tags) {
|
for (const tagsKey in tags) {
|
||||||
|
|
|
@ -378,6 +378,25 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
* const errors = []
|
* const errors = []
|
||||||
* RewriteSpecial.convertIfNeeded({"special": {}}, errors, "test") // => undefined
|
* RewriteSpecial.convertIfNeeded({"special": {}}, errors, "test") // => undefined
|
||||||
* errors // => ["A 'special'-block should define 'type' to indicate which visualisation should be used"]
|
* errors // => ["A 'special'-block should define 'type' to indicate which visualisation should be used"]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* // an actual test
|
||||||
|
* const special = {"special": {
|
||||||
|
* "type": "multi",
|
||||||
|
* "before": {
|
||||||
|
* "en": "<h3>Entrances</h3>This building has {_entrances_count} entrances:"
|
||||||
|
* },
|
||||||
|
* "after": {
|
||||||
|
* "en": "{_entrances_count_without_width_count} entrances don't have width information yet"
|
||||||
|
* },
|
||||||
|
* "key": "_entrance_properties_with_width",
|
||||||
|
* "tagrendering": {
|
||||||
|
* "en": "An <a href='#{id}'>entrance</a> of {canonical(width)}"
|
||||||
|
* }
|
||||||
|
* }}
|
||||||
|
* const errors = []
|
||||||
|
* RewriteSpecial.convertIfNeeded(special, errors, "test") // => {"en": "<h3>Entrances</h3>This building has {_entrances_count} entrances: {multi(_entrance_properties_with_width,An <a href='#&LBRACEid&RBRACE'>entrance</a> of &LBRACEcanonical&LPARENSwidth&RPARENS&RBRACE)}An <a href='#{id}'>entrance</a> of {canonical(width)}"}
|
||||||
|
* errors // => []
|
||||||
*/
|
*/
|
||||||
private static convertIfNeeded(input: (object & { special: { type: string } }) | any, errors: string[], context: string): any {
|
private static convertIfNeeded(input: (object & { special: { type: string } }) | any, errors: string[], context: string): any {
|
||||||
const special = input["special"]
|
const special = input["special"]
|
||||||
|
@ -385,10 +404,6 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const wrongKey of Object.keys(input).filter(k => k !== "special" && k !== "before" && k !== "after")) {
|
|
||||||
errors.push(`At ${context}: Unexpected key in a special block: ${wrongKey}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = special["type"]
|
const type = special["type"]
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
errors.push("A 'special'-block should define 'type' to indicate which visualisation should be used")
|
errors.push("A 'special'-block should define 'type' to indicate which visualisation should be used")
|
||||||
|
@ -406,10 +421,10 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
// Check for obsolete and misspelled arguments
|
// Check for obsolete and misspelled arguments
|
||||||
errors.push(...Object.keys(special)
|
errors.push(...Object.keys(special)
|
||||||
.filter(k => !argNames.has(k))
|
.filter(k => !argNames.has(k))
|
||||||
.filter(k => k !== "type")
|
.filter(k => k !== "type" && k !== "before" && k !== "after")
|
||||||
.map(wrongArg => {
|
.map(wrongArg => {
|
||||||
const byDistance = Utils.sortedByLevenshteinDistance(wrongArg, argNamesList, x => x)
|
const byDistance = Utils.sortedByLevenshteinDistance(wrongArg, argNamesList, x => x)
|
||||||
return `Unexpected argument with name '${wrongArg}'. Did you mean ${byDistance[0]}?\n\tAll known arguments are ${argNamesList.join(", ")}`;
|
return `Unexpected argument in special block at ${context} with name '${wrongArg}'. Did you mean ${byDistance[0]}?\n\tAll known arguments are ${argNamesList.join(", ")}`;
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Check that all obligated arguments are present. They are obligated if they don't have a preset value
|
// Check that all obligated arguments are present. They are obligated if they don't have a preset value
|
||||||
|
@ -496,12 +511,21 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
* const expected = {render: {'*': "{image_carousel(image)}"}, mappings: [{if: "other_image_key", then: {'*': "{image_carousel(other_image_key)}"}} ]}
|
* const expected = {render: {'*': "{image_carousel(image)}"}, mappings: [{if: "other_image_key", then: {'*': "{image_carousel(other_image_key)}"}} ]}
|
||||||
* result // => expected
|
* result // => expected
|
||||||
*
|
*
|
||||||
|
* // Should put text before if specified
|
||||||
* const tr = {
|
* const tr = {
|
||||||
* render: {special: {type: "image_carousel", image_key: "image"}, before: {en: "Some introduction"} },
|
* render: {special: {type: "image_carousel", image_key: "image"}, before: {en: "Some introduction"} },
|
||||||
* }
|
* }
|
||||||
* const result = new RewriteSpecial().convert(tr,"test").result
|
* const result = new RewriteSpecial().convert(tr,"test").result
|
||||||
* const expected = {render: {'en': "Some introduction{image_carousel(image)}"}}
|
* const expected = {render: {'en': "Some introduction{image_carousel(image)}"}}
|
||||||
* result // => expected
|
* result // => expected
|
||||||
|
*
|
||||||
|
* // Should put text after if specified
|
||||||
|
* const tr = {
|
||||||
|
* render: {special: {type: "image_carousel", image_key: "image"}, after: {en: "Some footer"} },
|
||||||
|
* }
|
||||||
|
* const result = new RewriteSpecial().convert(tr,"test").result
|
||||||
|
* const expected = {render: {'en': "{image_carousel(image)}Some footer"}}
|
||||||
|
* result // => expected
|
||||||
*/
|
*/
|
||||||
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
|
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||||
const errors = []
|
const errors = []
|
||||||
|
|
|
@ -42,7 +42,6 @@ import NoteCommentElement from "./Popup/NoteCommentElement";
|
||||||
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader";
|
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader";
|
||||||
import FileSelectorButton from "./Input/FileSelectorButton";
|
import FileSelectorButton from "./Input/FileSelectorButton";
|
||||||
import {LoginToggle} from "./Popup/LoginButton";
|
import {LoginToggle} from "./Popup/LoginButton";
|
||||||
import {start} from "repl";
|
|
||||||
import {SubstitutedTranslation} from "./SubstitutedTranslation";
|
import {SubstitutedTranslation} from "./SubstitutedTranslation";
|
||||||
import {TextField} from "./Input/TextField";
|
import {TextField} from "./Input/TextField";
|
||||||
import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata";
|
import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata";
|
||||||
|
@ -60,7 +59,8 @@ import Slider from "./Input/Slider";
|
||||||
import List from "./Base/List";
|
import List from "./Base/List";
|
||||||
import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
||||||
import {OsmFeature} from "../Models/OsmFeature";
|
import {OsmFeature} from "../Models/OsmFeature";
|
||||||
import Link from "./Base/Link";
|
import EditableTagRendering from "./Popup/EditableTagRendering";
|
||||||
|
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
|
@ -334,6 +334,13 @@ export default class SpecialVisualizations {
|
||||||
render: {
|
render: {
|
||||||
special: {
|
special: {
|
||||||
type: "some_special_visualisation",
|
type: "some_special_visualisation",
|
||||||
|
before: {
|
||||||
|
en: "Some text to prefix before the special element (e.g. a title)",
|
||||||
|
nl: "Een tekst om voor het element te zetten (bv. een titel)"
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
en: "Some text to put after the element, e.g. a footer"
|
||||||
|
},
|
||||||
"argname": "some_arg",
|
"argname": "some_arg",
|
||||||
"message": {
|
"message": {
|
||||||
en: "some other really long message",
|
en: "some other really long message",
|
||||||
|
@ -1206,7 +1213,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
funcName: "multi",
|
funcName: "multi",
|
||||||
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
||||||
example: "```json\n"+JSON.stringify({
|
example: "```json\n" + JSON.stringify({
|
||||||
render: {
|
render: {
|
||||||
special: {
|
special: {
|
||||||
type: "multi",
|
type: "multi",
|
||||||
|
@ -1216,20 +1223,81 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, null, " ")+"```",
|
}, null, " ") + "```",
|
||||||
args: [
|
args: [
|
||||||
{name: "key",
|
|
||||||
doc: "The property to read and to interpret as a list of properties"},
|
|
||||||
{
|
{
|
||||||
name:"tagrendering",
|
name: "key",
|
||||||
doc: "An entire tagRenderingConfig"
|
doc: "The property to read and to interpret as a list of properties",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tagrendering",
|
||||||
|
doc: "An entire tagRenderingConfig",
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
,
|
,
|
||||||
constr(state, featureTags, args) {
|
constr(state, featureTags, args) {
|
||||||
const [key, tr] = args
|
const [key, tr] = args
|
||||||
console.log("MULTI: ", key, tr)
|
const translation = new Translation({"*": tr})
|
||||||
return undefined
|
return new VariableUiElement(featureTags.map(tags => {
|
||||||
|
const properties: object[] = JSON.parse(tags[key])
|
||||||
|
const elements = []
|
||||||
|
for (const property of properties) {
|
||||||
|
const subsTr = new SubstitutedTranslation(translation, new UIEventSource<any>(property), state)
|
||||||
|
elements.push(subsTr)
|
||||||
|
}
|
||||||
|
return new List(elements)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "steal",
|
||||||
|
docs: "Shows a tagRendering from a different object as if this was the object itself",
|
||||||
|
args: [{
|
||||||
|
name: "featureId",
|
||||||
|
doc: "The key of the attribute which contains the id of the feature from which to use the tags",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tagRenderingId",
|
||||||
|
doc: "The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection",
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
constr(state, featureTags, args) {
|
||||||
|
const [featureIdKey, layerAndtagRenderingIds] = args
|
||||||
|
const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
|
||||||
|
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
|
||||||
|
const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".")
|
||||||
|
const layer = state.layoutToUse.layers.find(l => l.id === layerId)
|
||||||
|
const tagRendering = layer.tagRenderings.find(tr => tr.id === tagRenderingId)
|
||||||
|
tagRenderings.push([layer, tagRendering])
|
||||||
|
}
|
||||||
|
return new VariableUiElement(featureTags.map(tags => {
|
||||||
|
const featureId = tags[featureIdKey]
|
||||||
|
if (featureId === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const otherTags = state.allElements.getEventSourceById(featureId)
|
||||||
|
const elements: BaseUIElement[] = []
|
||||||
|
for (const [layer, tagRendering] of tagRenderings) {
|
||||||
|
const el = new EditableTagRendering(otherTags, tagRendering, layer.units, state, {})
|
||||||
|
elements.push(el)
|
||||||
|
}
|
||||||
|
if (elements.length === 1) {
|
||||||
|
return elements[0]
|
||||||
|
}
|
||||||
|
return new Combine(elements).SetClass("flex flex-col");
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
getLayerDependencies(args): string[] {
|
||||||
|
const [_, tagRenderingId] = args
|
||||||
|
if (tagRenderingId.indexOf(".") < 0) {
|
||||||
|
throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot"
|
||||||
|
}
|
||||||
|
const [layerId, __] = tagRenderingId.split(".")
|
||||||
|
return [layerId]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -47,33 +47,84 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"calculatedTags": [
|
"calculatedTags": [
|
||||||
"_entrance_properties=feat.overlapWith('entrance')?.map(e => e.feat.properties).filter(p => p !== undefined).filter(p => p.width !== undefined)",
|
"_entrance_properties=feat.overlapWith('entrance')?.map(e => e.feat.properties)?.filter(p => p !== undefined && p.indoor !== 'door')",
|
||||||
"_entrance:id=feat.get('_entrance_properties')?.map(e => e.id)?.at(0)",
|
"_entrance_properties_with_width=feat.get('_entrance_properties')?.filter(p => p['width'] !== undefined)",
|
||||||
"_entrance:width=feat.get('_entrance_properties')?.map(e => e.width)?.at(0)"
|
"_entrances_count=feat.get('_entrance_properties').length",
|
||||||
|
"_entrances_count_without_width_count= feat.get('_entrances_count') - feat.get('_entrance_properties_with_width').length",
|
||||||
|
"_biggest_width= Math.max( feat.get('_entrance_properties').map(p => p.width))",
|
||||||
|
"_biggest_width_properties= /* Can be a list! */ feat.get('_entrance_properties').filter(p => p.width === feat.get('_biggest_width'))",
|
||||||
|
"_biggest_width_id=feat.get('_biggest_width_properties').id"
|
||||||
],
|
],
|
||||||
"tagRenderings": [
|
"units": [
|
||||||
{
|
{
|
||||||
"id": "_entrance:width",
|
"appliesToKey": [
|
||||||
"render": {
|
"width","_biggest_width"
|
||||||
"en": "<a href ='#{_entrance:id} '>This door has a width of {canonical(_entrance:width)} meters </a>",
|
],
|
||||||
"nl": "<a href ='#{_entrance:id} '>Deze deur heeft een breedte van {canonical(_entrance:width)} meter </a>",
|
"applicableUnits": [
|
||||||
"de": "<a href ='#{_entrance:id} '>Diese Tür hat eine Durchgangsbreite von {canonical(_entrance:width)} Meter </a>",
|
|
||||||
"es": "<a href ='#{_entrance:id} '>Esta puerta tiene una ancho de {canonical(_entrance:width)} metros </a>",
|
|
||||||
"fr": "<a href ='#{_entrance:id} '>Cette porte a une largeur de {canonical(_entrance:width)} mètres </a>"
|
|
||||||
},
|
|
||||||
"freeform": {
|
|
||||||
"key": "_entrance:width"
|
|
||||||
},
|
|
||||||
"mappings": [
|
|
||||||
{
|
{
|
||||||
"if": "_entrance:width=",
|
"canonicalDenomination": "m",
|
||||||
"then": {
|
"alternativeDenomination": [
|
||||||
"en": "This entrance has no width information",
|
"meter"
|
||||||
"de": "Der Eingang hat keine Informationen zur Durchgangsbreite",
|
],
|
||||||
"fr": "Cette entrée n'a pas d'informations sur sa largeur"
|
"human": {
|
||||||
|
"en": "meter",
|
||||||
|
"fr": "mètre",
|
||||||
|
"de": "Meter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"canonicalDenomination": "cm",
|
||||||
|
"alternativeDenomination": [
|
||||||
|
"centimeter",
|
||||||
|
"cms"
|
||||||
|
],
|
||||||
|
"human": {
|
||||||
|
"en": "centimeter",
|
||||||
|
"fr": "centimètre",
|
||||||
|
"de": "Zentimeter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"tagRenderings": [
|
||||||
|
{
|
||||||
|
"id": "entrance_info",
|
||||||
|
"render": {
|
||||||
|
"before": {
|
||||||
|
"en": "<h3>Entrances</h3>This building has {_entrances_count} entrances:"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"en": "{_entrances_count_without_width_count} entrances don't have width information yet"
|
||||||
|
},
|
||||||
|
"special": {
|
||||||
|
"type": "multi",
|
||||||
|
"key": "_entrance_properties_with_width",
|
||||||
|
"tagrendering": {
|
||||||
|
"en": "An <a href='#{id}'>entrance</a> of {canonical(width)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"if": "_entrances_count=0",
|
||||||
|
"then": {
|
||||||
|
"en": "No entrance has been marked"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": "_entrances_count_without_width:=_entrances_count",
|
||||||
|
"then": {
|
||||||
|
"en": "None of the {_entrance_count} entrances have width information yet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "biggest_width",
|
||||||
|
"render": "The <a href='#{_biggest_width_id}'>entrance with the biggest width</a> is {canonical(_biggest_width)} wide",
|
||||||
|
"condition": "_biggest_width_id~*"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -17,6 +17,12 @@
|
||||||
"startZoom": 14,
|
"startZoom": 14,
|
||||||
"widenFactor": 2,
|
"widenFactor": 2,
|
||||||
"layers": [
|
"layers": [
|
||||||
"indoors"
|
"indoors",
|
||||||
|
{
|
||||||
|
"builtin": ["walls_and_buildings"],
|
||||||
|
"override": {
|
||||||
|
"shownByDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -366,15 +366,11 @@
|
||||||
],
|
],
|
||||||
"overrideAll": {
|
"overrideAll": {
|
||||||
"+calculatedTags": [
|
"+calculatedTags": [
|
||||||
"_poi_walls_and_buildings_entrance_properties=[].concat(...feat.closestn('walls_and_buildings',1, undefined, 500).map(w => ({id: w.feat.properties.id, width: w.feat.properties['_entrance_properties']})))",
|
"_enclosing_building=feat.enclosingFeatures('walls_and_buildings')?.map(f => f.feat.properties.id)?.at(0)"
|
||||||
"_poi_walls_and_buildings_entrance_count=[].concat(...feat.overlapWith('walls_and_buildings').map(w => ({id: w.feat.properties.id, width: w.feat.properties['_entrance_properties']})))",
|
|
||||||
"_poi_walls_and_buildings_entrance_properties_with_width=feat.get('_poi_walls_and_buildings_entrance_properties').filter(p => p['width'] !== undefined)",
|
|
||||||
"_poi_entrance:id=JSON.parse(feat.properties._poi_walls_and_buildings_entrance_properteis)?.id",
|
|
||||||
"_poi_entrance:width=JSON.parse(feat.properties._poi_walls_and_buildings_entrance_properties)?.width"
|
|
||||||
],
|
],
|
||||||
"+tagRenderings": [
|
"tagRenderings+": [
|
||||||
{
|
{
|
||||||
"id": "_containing_poi_entrance:width",
|
"id": "_stolen_entrances",
|
||||||
"condition": {
|
"condition": {
|
||||||
"and": [
|
"and": [
|
||||||
"entrance=",
|
"entrance=",
|
||||||
|
@ -383,21 +379,11 @@
|
||||||
"door="
|
"door="
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mappings": [{
|
|
||||||
"if": "_poi_walls_and_buildings_entrance_properties_with_width=[]",
|
|
||||||
"then": {
|
|
||||||
"en": "The containing building has {}"
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"render": {
|
"render": {
|
||||||
"special": {
|
"special": {
|
||||||
"type": "multi",
|
"type": "steal",
|
||||||
"key": "_poi_walls_and_buildings_entrance_properties",
|
"featureId": "_enclosing_building",
|
||||||
"tagrendering": {
|
"tagRenderingId": "walls_and_buildings.entrance_info; walls_and_buildings.biggest_width"
|
||||||
"en": "The containing building can be entered via <a href='#{_poi_entrance:id}'>a door of {canonical(_poi_entrance:width)}</a>",
|
|
||||||
"fr": "On peut entrer dans ce batiment via <a href='#{_poi_entrance:id}'>une porte de {canonical(_poi_entrance:width)}</a>",
|
|
||||||
"de": "Das Gebäude kann über <a href='#{_poi_entrance:id}'>durch eine Tür von {canonical(_poi_entrance:width)} betreten werden.</a>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {describe} from 'mocha'
|
import {describe} from 'mocha'
|
||||||
import {expect} from 'chai'
|
import {expect} from 'chai'
|
||||||
import {Utils} from "../Utils";
|
|
||||||
|
|
||||||
describe("TestSuite", () => {
|
describe("TestSuite", () => {
|
||||||
|
|
||||||
|
|
243
test/Logic/ExtraFunctions.spec.ts
Normal file
243
test/Logic/ExtraFunctions.spec.ts
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
import {describe} from 'mocha'
|
||||||
|
import {expect} from 'chai'
|
||||||
|
import {ExtraFuncParams, ExtraFunctions} from "../../Logic/ExtraFunctions";
|
||||||
|
import {OsmFeature} from "../../Models/OsmFeature";
|
||||||
|
|
||||||
|
|
||||||
|
describe("OverlapFunc", () => {
|
||||||
|
|
||||||
|
it("should give doors on the edge", () => {
|
||||||
|
const door: OsmFeature = {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "node/9909268725",
|
||||||
|
"properties": {
|
||||||
|
"automatic_door": "no",
|
||||||
|
"door": "hinged",
|
||||||
|
"indoor": "door",
|
||||||
|
"kerb:height": "0 cm",
|
||||||
|
"width": "1",
|
||||||
|
"id": "node/9909268725",
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
4.3494436,
|
||||||
|
50.8657928
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const hermanTeirlinck = {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": "way/444059131",
|
||||||
|
"properties": {
|
||||||
|
"timestamp": "2022-07-27T15:15:01Z",
|
||||||
|
"version": 27,
|
||||||
|
"changeset": 124146283,
|
||||||
|
"user": "Pieter Vander Vennet",
|
||||||
|
"uid": 3818858,
|
||||||
|
"addr:city": "Bruxelles - Brussel",
|
||||||
|
"addr:housenumber": "88",
|
||||||
|
"addr:postcode": "1000",
|
||||||
|
"addr:street": "Avenue du Port - Havenlaan",
|
||||||
|
"building": "government",
|
||||||
|
"building:levels": "5",
|
||||||
|
"name": "Herman Teirlinckgebouw",
|
||||||
|
"operator": "Vlaamse overheid",
|
||||||
|
"wikidata": "Q47457146",
|
||||||
|
"wikipedia": "nl:Herman Teirlinckgebouw",
|
||||||
|
"id": "way/444059131",
|
||||||
|
"_backend": "https://www.openstreetmap.org",
|
||||||
|
"_lat": "50.86622355",
|
||||||
|
"_lon": "4.3501212",
|
||||||
|
"_layer": "walls_and_buildings",
|
||||||
|
"_length": "380.5933566256343",
|
||||||
|
"_length:km": "0.4",
|
||||||
|
"_now:date": "2022-07-29",
|
||||||
|
"_now:datetime": "2022-07-29 14:19:25",
|
||||||
|
"_loaded:date": "2022-07-29",
|
||||||
|
"_loaded:datetime": "2022-07-29 14:19:25",
|
||||||
|
"_last_edit:contributor": "Pieter Vander Vennet",
|
||||||
|
"_last_edit:contributor:uid": 3818858,
|
||||||
|
"_last_edit:changeset": 124146283,
|
||||||
|
"_last_edit:timestamp": "2022-07-27T15:15:01Z",
|
||||||
|
"_version_number": 27,
|
||||||
|
"_geometry:type": "Polygon",
|
||||||
|
"_surface": "7461.252251355437",
|
||||||
|
"_surface:ha": "0.7",
|
||||||
|
"_country": "be"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
4.3493369,
|
||||||
|
50.8658274
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3493393,
|
||||||
|
50.8658266
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3494436,
|
||||||
|
50.8657928
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3495272,
|
||||||
|
50.8657658
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.349623,
|
||||||
|
50.8657348
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3497442,
|
||||||
|
50.8656956
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3498441,
|
||||||
|
50.8656632
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3500768,
|
||||||
|
50.8655878
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3501619,
|
||||||
|
50.8656934
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3502113,
|
||||||
|
50.8657551
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3502729,
|
||||||
|
50.8658321
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3503063,
|
||||||
|
50.8658737
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3503397,
|
||||||
|
50.8659153
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3504159,
|
||||||
|
50.8660101
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3504177,
|
||||||
|
50.8660123
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3504354,
|
||||||
|
50.8660345
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3505348,
|
||||||
|
50.8661584
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3504935,
|
||||||
|
50.866172
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3506286,
|
||||||
|
50.8663405
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3506701,
|
||||||
|
50.8663271
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3508563,
|
||||||
|
50.8665592
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3509055,
|
||||||
|
50.8666206
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3506278,
|
||||||
|
50.8667104
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3504502,
|
||||||
|
50.8667675
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3503132,
|
||||||
|
50.8668115
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3502162,
|
||||||
|
50.8668427
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3501645,
|
||||||
|
50.8668593
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3499296,
|
||||||
|
50.8665664
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3498821,
|
||||||
|
50.8665073
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3498383,
|
||||||
|
50.8664527
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3498126,
|
||||||
|
50.8664207
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3497459,
|
||||||
|
50.8663376
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3497227,
|
||||||
|
50.8663086
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3496517,
|
||||||
|
50.8662201
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3495158,
|
||||||
|
50.8660507
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4.3493369,
|
||||||
|
50.8658274
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bbox": {
|
||||||
|
"maxLat": 50.8668593,
|
||||||
|
"maxLon": 4.3509055,
|
||||||
|
"minLat": 50.8655878,
|
||||||
|
"minLon": 4.3493369
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: ExtraFuncParams = {
|
||||||
|
getFeatureById: id => undefined,
|
||||||
|
getFeaturesWithin: () => [[door]],
|
||||||
|
memberships: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ExtraFunctions.FullPatchFeature(params, hermanTeirlinck)
|
||||||
|
const overlap = (<any>hermanTeirlinck).overlapWith("*")
|
||||||
|
console.log(JSON.stringify(overlap))
|
||||||
|
expect(overlap[0].feat == door).true
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in a new issue