forked from MapComplete/MapComplete
		
	Merge feature branch
This commit is contained in:
		
						commit
						d5d11d48b5
					
				
					 12 changed files with 487 additions and 69 deletions
				
			
		|  | @ -1,5 +1,6 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  Metatags  |  Metatags  | ||||||
| ========== | ========== | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -187,7 +195,7 @@ class DistanceToFunc implements ExtraFunction { | ||||||
| 
 | 
 | ||||||
| class ClosestObjectFunc implements ExtraFunction { | class ClosestObjectFunc implements ExtraFunction { | ||||||
|     _name = "closest" |     _name = "closest" | ||||||
|     _doc = "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet laoded)" |     _doc = "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. Returns a single geojson feature or undefined if nothing is found (or not yet loaded)" | ||||||
| 
 | 
 | ||||||
|     _args = ["list of features or a layer name or '*' to get all features"] |     _args = ["list of features or a layer name or '*' to get all features"] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
|  | @ -469,6 +484,8 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|                         .replace(/\{/g, "&LBRACE") |                         .replace(/\{/g, "&LBRACE") | ||||||
|                         .replace(/}/g, "&RBRACE") |                         .replace(/}/g, "&RBRACE") | ||||||
|                     args.push(txt) |                     args.push(txt) | ||||||
|  |                 } else if(typeof v === "object"){ | ||||||
|  |                     args.push(JSON.stringify(v)) | ||||||
|                 } else { |                 } else { | ||||||
|                     args.push(v) |                     args.push(v) | ||||||
|                 } |                 } | ||||||
|  | @ -494,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", | ||||||
|  | @ -1202,6 +1209,96 @@ export default class SpecialVisualizations { | ||||||
| 
 | 
 | ||||||
|                         })) |                         })) | ||||||
|                     } |                     } | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     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", | ||||||
|  |                     example: "```json\n" + JSON.stringify({ | ||||||
|  |                         render: { | ||||||
|  |                             special: { | ||||||
|  |                                 type: "multi", | ||||||
|  |                                 key: "_doors_from_building_properties", | ||||||
|  |                                 tagRendering: { | ||||||
|  |                                     render: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}" | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }, null, "  ") + "```", | ||||||
|  |                     args: [ | ||||||
|  |                         { | ||||||
|  |                             name: "key", | ||||||
|  |                             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) { | ||||||
|  |                         const [key, tr] = args | ||||||
|  |                         const translation = new Translation({"*": tr}) | ||||||
|  |                         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,34 +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": { | ||||||
|             "nl": "Deze toegang heeft geen informatie over deurbreedte" |             "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 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  | @ -369,13 +369,11 @@ | ||||||
|   ], |   ], | ||||||
|   "overrideAll": { |   "overrideAll": { | ||||||
|     "+calculatedTags": [ |     "+calculatedTags": [ | ||||||
|       "_poi_walls_and_buildings_entrance_properties=feat.closestn('walls_and_buildings', 1, undefined, 1000).map(w => ({id: w.feat.properties.id, width: w.feat.properties['_entrance:width']}))[0]", |       "_enclosing_building=feat.enclosingFeatures('walls_and_buildings')?.map(f => f.feat.properties.id)?.at(0)" | ||||||
|       "_poi_entrance:id=JSON.parce(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=", | ||||||
|  | @ -385,26 +383,12 @@ | ||||||
|           ] |           ] | ||||||
|         }, |         }, | ||||||
|         "render": { |         "render": { | ||||||
|           "en": "The containing building can be entered via <a href='#{_poi_entrance:id}'>a door of {canonical(_poi_entrance:width)}</a>", |           "special": { | ||||||
|           "nl": "Het gebouw waarin dit zich bevindt kan binnengegaan worden <a href='#{_poi_entrance:id}'>via een deur</a> die {canonical(_poi_entrance:width)} breed is", |             "type": "steal", | ||||||
|           "fr": "On peut entrer dans ce batiment via <a href='#{_poi_entrance:id}'>une porte de {canonical(_poi_entrance:width)}</a>", |             "featureId": "_enclosing_building", | ||||||
|           "de": "Das Gebäude kann über <a href='#{_poi_entrance:id}'>durch eine Tür von {canonical(_poi_entrance:width)} betreten werden.</a>" |             "tagRenderingId": "walls_and_buildings.entrance_info; walls_and_buildings.biggest_width" | ||||||
|         }, |  | ||||||
|         "freeform": { |  | ||||||
|           "key": "_poi_entrance:width", |  | ||||||
|           "type": "distance" |  | ||||||
|         }, |  | ||||||
|         "mappings": [ |  | ||||||
|           { |  | ||||||
|             "if": "_poi_entrance:width=", |  | ||||||
|             "then": { |  | ||||||
|               "nl": "Het omvattende gebouw heeft geen gekende deurbreedtes. Voeg een deur en breedte toe.", |  | ||||||
|               "en": "The containing building has no information on door widths. Add a door and measure the width to get information", |  | ||||||
|               "fr": "Ce bâtiment n'a aucune information sur les largeurs de portes. Ajoutez une porte et mesurez la largeur pour obtenir des informations", |  | ||||||
|               "de": "Das Gebäude hat keine Informationen über Türbreiten. Fügen Sie eine Tür hinzu und messen Sie die Breite, um Informationen zu erhalten" |  | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         ] |  | ||||||
|       } |       } | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue