Add switch to hide certain features, cleanup of code
This commit is contained in:
parent
aa0989b72a
commit
1b1ec9f15d
18 changed files with 230 additions and 173 deletions
|
@ -24,6 +24,7 @@ export default class LayerConfig {
|
||||||
static WAYHANDLING_DEFAULT = 0;
|
static WAYHANDLING_DEFAULT = 0;
|
||||||
static WAYHANDLING_CENTER_ONLY = 1;
|
static WAYHANDLING_CENTER_ONLY = 1;
|
||||||
static WAYHANDLING_CENTER_AND_WAY = 2;
|
static WAYHANDLING_CENTER_AND_WAY = 2;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
name: Translation
|
name: Translation
|
||||||
description: Translation;
|
description: Translation;
|
||||||
|
@ -31,6 +32,7 @@ export default class LayerConfig {
|
||||||
calculatedTags: [string, string][]
|
calculatedTags: [string, string][]
|
||||||
doNotDownload: boolean;
|
doNotDownload: boolean;
|
||||||
passAllFeatures: boolean;
|
passAllFeatures: boolean;
|
||||||
|
isShown: TagRenderingConfig;
|
||||||
minzoom: number;
|
minzoom: number;
|
||||||
maxzoom: number;
|
maxzoom: number;
|
||||||
title?: TagRenderingConfig;
|
title?: TagRenderingConfig;
|
||||||
|
@ -205,6 +207,7 @@ export default class LayerConfig {
|
||||||
throw "Builtin SVG asset not found: " + iconPath
|
throw "Builtin SVG asset not found: " + iconPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.isShown = tr("isShown", "yes");
|
||||||
this.iconSize = tr("iconSize", "40,40,center");
|
this.iconSize = tr("iconSize", "40,40,center");
|
||||||
this.color = tr("color", "#0000ff");
|
this.color = tr("color", "#0000ff");
|
||||||
this.width = tr("width", "7");
|
this.width = tr("width", "7");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||||
|
import TagRenderingConfig from "./TagRenderingConfig";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for a single layer
|
* Configuration for a single layer
|
||||||
|
@ -52,6 +53,14 @@ export interface LayerConfigJson {
|
||||||
*/
|
*/
|
||||||
doNotDownload?: boolean;
|
doNotDownload?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tagrendering should either be 'yes' or 'no'. If 'no' is returned, then the feature will be hidden from view.
|
||||||
|
* This is useful to hide certain features from view
|
||||||
|
* The default value is 'yes'
|
||||||
|
*/
|
||||||
|
isShown?: TagRenderingConfigJson;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The zoomlevel at which point the data is shown and loaded.
|
* The zoomlevel at which point the data is shown and loaded.
|
||||||
* Default: 0
|
* Default: 0
|
||||||
|
|
|
@ -384,26 +384,7 @@ export class InitUiElements {
|
||||||
State.state.layerUpdater = updater;
|
State.state.layerUpdater = updater;
|
||||||
const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl);
|
const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl);
|
||||||
|
|
||||||
|
new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse);
|
||||||
source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => {
|
|
||||||
if (featuresFreshness === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
featuresFreshness.forEach(featureFresh => {
|
|
||||||
const feature = featureFresh.feature;
|
|
||||||
State.state.allElements.addOrGetElement(feature);
|
|
||||||
|
|
||||||
if (Hash.hash.data === feature.properties.id) {
|
|
||||||
State.state.selectedElement.setData(feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
MetaTagging.addMetatags(featuresFreshness, state.layoutToUse.data.layers);
|
|
||||||
})
|
|
||||||
|
|
||||||
new ShowDataLayer(source.features, State.state.leafletMap,
|
|
||||||
State.state.layoutToUse);
|
|
||||||
|
|
||||||
new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source);
|
new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source);
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ class TitleElement extends UIElement {
|
||||||
}
|
}
|
||||||
if (layer.source.osmTags.matchesProperties(properties)) {
|
if (layer.source.osmTags.matchesProperties(properties)) {
|
||||||
const title = new TagRenderingAnswer(
|
const title = new TagRenderingAnswer(
|
||||||
this._allElementsStorage.getEventSourceFor(feature),
|
this._allElementsStorage.addOrGetElement(feature),
|
||||||
layer.title
|
layer.title
|
||||||
)
|
)
|
||||||
return new Combine([defaultTitle, " | ", title]).Render();
|
return new Combine([defaultTitle, " | ", title]).Render();
|
||||||
|
|
|
@ -15,30 +15,42 @@ export class ElementStorage {
|
||||||
this._elements[id] = eventSource;
|
this._elements[id] = eventSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
addElement(element): UIEventSource<any> {
|
/**
|
||||||
const eventSource = new UIEventSource<any>(element.properties, "tags of "+element.properties.id);
|
* Creates a UIEventSource for the tags of the given feature.
|
||||||
this._elements[element.properties.id] = eventSource;
|
* If an UIEventsource has been created previously, the same UIEventSource will be returned
|
||||||
return eventSource;
|
*
|
||||||
}
|
* Note: it will cleverly merge the tags, if needed
|
||||||
|
*/
|
||||||
addOrGetElement(element: any) : UIEventSource<any>{
|
addOrGetElement(feature: any): UIEventSource<any> {
|
||||||
const elementId = element.properties.id;
|
const elementId = feature.properties.id;
|
||||||
if (elementId in this._elements) {
|
if (elementId in this._elements) {
|
||||||
const es = this._elements[elementId];
|
const es = this._elements[elementId];
|
||||||
|
if (es.data == feature.properties) {
|
||||||
|
// Reference comparison gives the same object! we can just return the event source
|
||||||
|
return es;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const keptKeys = es.data;
|
const keptKeys = es.data;
|
||||||
// The element already exists
|
// The element already exists
|
||||||
// We add all the new keys to the old keys
|
// We add all the new keys to the old keys
|
||||||
for (const k in element.properties) {
|
let somethingChanged = false;
|
||||||
const v = element.properties[k];
|
for (const k in feature.properties) {
|
||||||
|
const v = feature.properties[k];
|
||||||
if (keptKeys[k] !== v) {
|
if (keptKeys[k] !== v) {
|
||||||
keptKeys[k] = v;
|
keptKeys[k] = v;
|
||||||
es.ping();
|
somethingChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (somethingChanged) {
|
||||||
|
es.ping();
|
||||||
|
}
|
||||||
|
|
||||||
return es;
|
return es;
|
||||||
}else{
|
} else {
|
||||||
return this.addElement(element);
|
const eventSource = new UIEventSource<any>(feature.properties, "tags of " + feature.properties.id);
|
||||||
|
this._elements[feature.properties.id] = eventSource;
|
||||||
|
return eventSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,8 +60,4 @@ export class ElementStorage {
|
||||||
}
|
}
|
||||||
console.error("Can not find eventsource with id ", elementId);
|
console.error("Can not find eventsource with id ", elementId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventSourceFor(feature): UIEventSource<any> {
|
|
||||||
return this.getEventSourceById(feature.properties.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
81
Logic/ExtraFunction.ts
Normal file
81
Logic/ExtraFunction.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import {GeoOperations} from "./GeoOperations";
|
||||||
|
import {UIElement} from "../UI/UIElement";
|
||||||
|
import Combine from "../UI/Base/Combine";
|
||||||
|
|
||||||
|
export class ExtraFunction {
|
||||||
|
|
||||||
|
|
||||||
|
private static DistanceToFunc = new ExtraFunction(
|
||||||
|
"distanceTo",
|
||||||
|
"Calculates the distance between the feature and a specified point",
|
||||||
|
["longitude", "latitude"],
|
||||||
|
(feature) => {
|
||||||
|
return (lon, lat) => {
|
||||||
|
// Feature._lon and ._lat is conveniently place by one of the other metatags
|
||||||
|
return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc];
|
||||||
|
private readonly _name: string;
|
||||||
|
private readonly _args: string[];
|
||||||
|
private readonly _doc: string;
|
||||||
|
private readonly _f: (feat: any) => any;
|
||||||
|
|
||||||
|
constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) {
|
||||||
|
this._name = name;
|
||||||
|
this._doc = doc;
|
||||||
|
this._args = args;
|
||||||
|
this._f = f;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FullPatchFeature(feature) {
|
||||||
|
for (const func of ExtraFunction.allFuncs) {
|
||||||
|
func.PatchFeature(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HelpText(): UIElement {
|
||||||
|
return new Combine([
|
||||||
|
ExtraFunction.intro,
|
||||||
|
...ExtraFunction.allFuncs.map(func =>
|
||||||
|
new Combine([
|
||||||
|
"<h3>" + func._name + "</h3>",
|
||||||
|
func._doc,
|
||||||
|
"<ul>",
|
||||||
|
...func._args.map(arg => "<li>" + arg + "</li>"),
|
||||||
|
"</ul>"
|
||||||
|
])
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchFeature(feature: any) {
|
||||||
|
feature[this._name] = this._f(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly intro = `<h2>Calculating tags with Javascript</h2>
|
||||||
|
|
||||||
|
<p>In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. <b>_lat</b>, <b>lon</b>, <b>_country</b>), as detailed above.</p>
|
||||||
|
|
||||||
|
<p>It is also possible to calculate your own tags - but this requires some javascript knowledge. </p>
|
||||||
|
|
||||||
|
Before proceeding, some warnings:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li> DO NOT DO THIS AS BEGINNER</li>
|
||||||
|
<li> <b>Only do this if all other techniques fail</b>. This should <i>not</i> be done to create a rendering effect, only to calculate a specific value</li>
|
||||||
|
<li> <b>THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES</b>. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.</li>
|
||||||
|
</ul>
|
||||||
|
In the layer object, add a field <b>calculatedTags</b>, e.g.:
|
||||||
|
|
||||||
|
<div class="code">
|
||||||
|
"calculatedTags": {
|
||||||
|
"_someKey": "javascript-expression",
|
||||||
|
"name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",
|
||||||
|
"_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
/**
|
/**
|
||||||
* In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled)
|
* In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled)
|
||||||
* If this is the case, multiple objects with a different _matching_layer_id are generated.
|
* If this is the case, multiple objects with a different _matching_layer_id are generated.
|
||||||
* If not, the _feature_layter_id is added
|
* In any case, this featureSource marks the objects with _matching_layer_id
|
||||||
*/
|
*/
|
||||||
export default class FeatureDuplicatorPerLayer implements FeatureSource {
|
export default class FeatureDuplicatorPerLayer implements FeatureSource {
|
||||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import LocalStorageSource from "./LocalStorageSource";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
import GeoJsonSource from "./GeoJsonSource";
|
import GeoJsonSource from "./GeoJsonSource";
|
||||||
|
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
|
||||||
|
|
||||||
export default class FeaturePipeline implements FeatureSource {
|
export default class FeaturePipeline implements FeatureSource {
|
||||||
|
|
||||||
|
@ -25,42 +26,42 @@ export default class FeaturePipeline implements FeatureSource {
|
||||||
|
|
||||||
const amendedOverpassSource =
|
const amendedOverpassSource =
|
||||||
new RememberingSource(
|
new RememberingSource(
|
||||||
new WayHandlingApplyingFeatureSource(flayers,
|
|
||||||
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers,
|
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers,
|
||||||
new LocalStorageSaver(updater, layout)))
|
new LocalStorageSaver(updater, layout)))
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const geojsonSources: GeoJsonSource [] = []
|
const geojsonSources: GeoJsonSource [] = []
|
||||||
for (const flayer of flayers.data) {
|
for (const flayer of flayers.data) {
|
||||||
const sourceUrl = flayer.layerDef.source.geojsonSource
|
const sourceUrl = flayer.layerDef.source.geojsonSource
|
||||||
if (sourceUrl !== undefined) {
|
if (sourceUrl !== undefined) {
|
||||||
geojsonSources.push(new WayHandlingApplyingFeatureSource(flayers,
|
geojsonSources.push(
|
||||||
new GeoJsonSource(flayer.layerDef.id, sourceUrl)))
|
new GeoJsonSource(flayer.layerDef.id, sourceUrl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const amendedLocalStorageSource =
|
const amendedLocalStorageSource =
|
||||||
new RememberingSource(
|
new RememberingSource(
|
||||||
new WayHandlingApplyingFeatureSource(flayers,
|
|
||||||
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout)))
|
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout)))
|
||||||
));
|
);
|
||||||
|
|
||||||
newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints);
|
newPoints = new FeatureDuplicatorPerLayer(flayers, newPoints);
|
||||||
|
|
||||||
const merged = new FeatureSourceMerger([
|
const merged =
|
||||||
|
new MetaTaggingFeatureSource(
|
||||||
|
new FeatureSourceMerger([
|
||||||
amendedOverpassSource,
|
amendedOverpassSource,
|
||||||
amendedLocalStorageSource,
|
amendedLocalStorageSource,
|
||||||
newPoints,
|
newPoints,
|
||||||
...geojsonSources
|
...geojsonSources
|
||||||
]);
|
]));
|
||||||
|
|
||||||
const source =
|
const source =
|
||||||
|
new WayHandlingApplyingFeatureSource(flayers,
|
||||||
new FilteringFeatureSource(
|
new FilteringFeatureSource(
|
||||||
flayers,
|
flayers,
|
||||||
locationControl,
|
locationControl,
|
||||||
merged
|
merged
|
||||||
);
|
));
|
||||||
this.features = source.features;
|
this.features = source.features;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default class FilteringFeatureSource implements FeatureSource {
|
||||||
|
|
||||||
const newFeatures = features.filter(f => {
|
const newFeatures = features.filter(f => {
|
||||||
const layerId = f.feature._matching_layer_id;
|
const layerId = f.feature._matching_layer_id;
|
||||||
|
|
||||||
if (layerId !== undefined) {
|
if (layerId !== undefined) {
|
||||||
const layer: {
|
const layer: {
|
||||||
isDisplayed: UIEventSource<boolean>,
|
isDisplayed: UIEventSource<boolean>,
|
||||||
|
@ -38,6 +39,17 @@ export default class FilteringFeatureSource implements FeatureSource {
|
||||||
console.error("No layer found with id ", layerId);
|
console.error("No layer found with id ", layerId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isShown = layer.layerDef.isShown
|
||||||
|
const tags = f.feature.properties;
|
||||||
|
console.log("Is shown: ", isShown," known? ", isShown.IsKnown(tags), " result: ", isShown.GetRenderValue(tags).txt)
|
||||||
|
if(isShown.IsKnown(tags)){
|
||||||
|
const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt;
|
||||||
|
if(result !== "yes"){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (FilteringFeatureSource.showLayer(layer, location)) {
|
if (FilteringFeatureSource.showLayer(layer, location)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
30
Logic/FeatureSource/MetaTaggingFeatureSource.ts
Normal file
30
Logic/FeatureSource/MetaTaggingFeatureSource.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import FeatureSource from "./FeatureSource";
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import State from "../../State";
|
||||||
|
import Hash from "../Web/Hash";
|
||||||
|
import MetaTagging from "../MetaTagging";
|
||||||
|
|
||||||
|
export default class MetaTaggingFeatureSource implements FeatureSource {
|
||||||
|
features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{feature: any; freshness: Date}[]>(undefined);
|
||||||
|
|
||||||
|
constructor(source: FeatureSource) {
|
||||||
|
const self = this;
|
||||||
|
source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => {
|
||||||
|
if (featuresFreshness === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
featuresFreshness.forEach(featureFresh => {
|
||||||
|
const feature = featureFresh.feature;
|
||||||
|
State.state.allElements.addOrGetElement(feature);
|
||||||
|
|
||||||
|
if (Hash.hash.data === feature.properties.id) {
|
||||||
|
State.state.selectedElement.setData(feature);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
MetaTagging.addMetatags(featuresFreshness, State.state.layoutToUse.data.layers);
|
||||||
|
self.features.setData(featuresFreshness);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ import {UIEventSource} from "../UIEventSource";
|
||||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
import {GeoOperations} from "../GeoOperations";
|
import {GeoOperations} from "../GeoOperations";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the part of the pipeline which introduces extra points at the center of an area (but only if this is demanded by the wayhandling)
|
||||||
|
*/
|
||||||
export default class WayHandlingApplyingFeatureSource implements FeatureSource {
|
export default class WayHandlingApplyingFeatureSource implements FeatureSource {
|
||||||
features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
||||||
|
@ -46,6 +49,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the copy
|
||||||
const centerPoint = GeoOperations.centerpoint(feat);
|
const centerPoint = GeoOperations.centerpoint(feat);
|
||||||
centerPoint._matching_layer_id = feat._matching_layer_id;
|
centerPoint._matching_layer_id = feat._matching_layer_id;
|
||||||
newFeatures.push({feature: centerPoint, freshness: f.freshness});
|
newFeatures.push({feature: centerPoint, freshness: f.freshness});
|
||||||
|
|
|
@ -1,85 +1,6 @@
|
||||||
import {GeoOperations} from "./GeoOperations";
|
|
||||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
||||||
import SimpleMetaTagger from "./SimpleMetaTagger";
|
import SimpleMetaTagger from "./SimpleMetaTagger";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {ExtraFunction} from "./ExtraFunction";
|
||||||
import Combine from "../UI/Base/Combine";
|
|
||||||
|
|
||||||
export class ExtraFunction {
|
|
||||||
|
|
||||||
static readonly intro = `<h2>Calculating tags with Javascript</h2>
|
|
||||||
|
|
||||||
<p>In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. <b>_lat</b>, <b>lon</b>, <b>_country</b>), as detailed above.</p>
|
|
||||||
|
|
||||||
<p>It is also possible to calculate your own tags - but this requires some javascript knowledge. </p>
|
|
||||||
|
|
||||||
Before proceeding, some warnings:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li> DO NOT DO THIS AS BEGINNER</li>
|
|
||||||
<li> <b>Only do this if all other techniques fail</b>. This should <i>not</i> be done to create a rendering effect, only to calculate a specific value</li>
|
|
||||||
<li> <b>THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES</b>. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.</li>
|
|
||||||
</ul>
|
|
||||||
In the layer object, add a field <b>calculatedTags</b>, e.g.:
|
|
||||||
|
|
||||||
<div class="code">
|
|
||||||
"calculatedTags": {
|
|
||||||
"_someKey": "javascript-expression",
|
|
||||||
"name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",
|
|
||||||
"_distanceCloserThen3Km": "feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
private static DistanceToFunc = new ExtraFunction(
|
|
||||||
"distanceTo",
|
|
||||||
"Calculates the distance between the feature and a specified point",
|
|
||||||
["longitude", "latitude"],
|
|
||||||
(feature) => {
|
|
||||||
return (lon, lat) => {
|
|
||||||
// Feature._lon and ._lat is conveniently place by one of the other metatags
|
|
||||||
return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc];
|
|
||||||
private readonly _name: string;
|
|
||||||
private readonly _args: string[];
|
|
||||||
private readonly _doc: string;
|
|
||||||
private readonly _f: (feat: any) => any;
|
|
||||||
|
|
||||||
constructor(name: string, doc: string, args: string[], f: ((feat: any) => any)) {
|
|
||||||
this._name = name;
|
|
||||||
this._doc = doc;
|
|
||||||
this._args = args;
|
|
||||||
this._f = f;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FullPatchFeature(feature) {
|
|
||||||
for (const func of ExtraFunction.allFuncs) {
|
|
||||||
func.PatchFeature(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HelpText(): UIElement {
|
|
||||||
return new Combine([
|
|
||||||
ExtraFunction.intro,
|
|
||||||
...ExtraFunction.allFuncs.map(func =>
|
|
||||||
new Combine([
|
|
||||||
"<h3>" + func._name + "</h3>",
|
|
||||||
func._doc,
|
|
||||||
"<ul>",
|
|
||||||
...func._args.map(arg => "<li>" + arg + "</li>"),
|
|
||||||
"</ul>"
|
|
||||||
])
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchFeature(feature: any) {
|
|
||||||
feature[this._name] = this._f(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
|
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
|
||||||
|
@ -163,4 +84,6 @@ export default class MetaTagging {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import {TagsFilter} from "../TagsFilter";
|
||||||
* Interfaces overpass to get all the latest data
|
* Interfaces overpass to get all the latest data
|
||||||
*/
|
*/
|
||||||
export class Overpass {
|
export class Overpass {
|
||||||
private _filter: TagsFilter
|
|
||||||
public static testUrl: string = null
|
public static testUrl: string = null
|
||||||
|
private _filter: TagsFilter
|
||||||
private readonly _extraScripts: string[];
|
private readonly _extraScripts: string[];
|
||||||
|
|
||||||
constructor(filter: TagsFilter, extraScripts: string[]) {
|
constructor(filter: TagsFilter, extraScripts: string[]) {
|
||||||
|
@ -16,21 +16,6 @@ export class Overpass {
|
||||||
this._extraScripts = extraScripts;
|
this._extraScripts = extraScripts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private buildQuery(bbox: string): string {
|
|
||||||
const filters = this._filter.asOverpass()
|
|
||||||
let filter = ""
|
|
||||||
for (const filterOr of filters) {
|
|
||||||
filter += 'nwr' + filterOr + ';'
|
|
||||||
}
|
|
||||||
for (const extraScript of this._extraScripts){
|
|
||||||
filter += '('+extraScript+');';
|
|
||||||
}
|
|
||||||
const query =
|
|
||||||
'[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;'
|
|
||||||
return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void {
|
queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void {
|
||||||
|
|
||||||
let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
|
let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
|
||||||
|
@ -52,11 +37,27 @@ export class Overpass {
|
||||||
onFail("Runtime error (timeout)")
|
onFail("Runtime error (timeout)")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geojson = OsmToGeoJson.default(json);
|
const geojson = OsmToGeoJson.default(json);
|
||||||
console.log("Received geojson", geojson)
|
console.log("Received geojson", geojson)
|
||||||
const osmTime = new Date(json.osm3s.timestamp_osm_base);
|
const osmTime = new Date(json.osm3s.timestamp_osm_base);
|
||||||
continuation(geojson, osmTime);
|
continuation(geojson, osmTime);
|
||||||
|
|
||||||
}).fail(onFail)
|
}).fail(onFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildQuery(bbox: string): string {
|
||||||
|
const filters = this._filter.asOverpass()
|
||||||
|
let filter = ""
|
||||||
|
for (const filterOr of filters) {
|
||||||
|
filter += 'nwr' + filterOr + ';'
|
||||||
|
}
|
||||||
|
for (const extraScript of this._extraScripts) {
|
||||||
|
filter += '(' + extraScript + ');';
|
||||||
|
}
|
||||||
|
const query =
|
||||||
|
'[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;'
|
||||||
|
return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default class SimpleMetaTagger {
|
||||||
SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => {
|
SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => {
|
||||||
try {
|
try {
|
||||||
feature.properties["_country"] = countries[0].trim().toLowerCase();
|
feature.properties["_country"] = countries[0].trim().toLowerCase();
|
||||||
const tagsSource = State.state.allElements.getEventSourceFor(feature);
|
const tagsSource = State.state.allElements.addOrGetElement(feature);
|
||||||
tagsSource.ping();
|
tagsSource.ping();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
|
@ -77,7 +77,7 @@ export default class SimpleMetaTagger {
|
||||||
"If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
|
"If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
|
||||||
(feature => {
|
(feature => {
|
||||||
|
|
||||||
const tagsSource = State.state.allElements.getEventSourceFor(feature);
|
const tagsSource = State.state.allElements.addOrGetElement(feature);
|
||||||
tagsSource.addCallbackAndRun(tags => {
|
tagsSource.addCallbackAndRun(tags => {
|
||||||
if (tags.opening_hours === undefined || tags._country === undefined) {
|
if (tags.opening_hours === undefined || tags._country === undefined) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class ShowDataLayer {
|
||||||
marker.openPopup();
|
marker.openPopup();
|
||||||
|
|
||||||
const popup = marker.getPopup();
|
const popup = marker.getPopup();
|
||||||
const tags = State.state.allElements.getEventSourceFor(selected);
|
const tags = State.state.allElements.addOrGetElement(selected);
|
||||||
const layer: LayerConfig = this._layerDict[selected._matching_layer_id];
|
const layer: LayerConfig = this._layerDict[selected._matching_layer_id];
|
||||||
const infoBox = FeatureInfoBox.construct(tags, layer);
|
const infoBox = FeatureInfoBox.construct(tags, layer);
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ export default class ShowDataLayer {
|
||||||
|
|
||||||
|
|
||||||
private createStyleFor(feature) {
|
private createStyleFor(feature) {
|
||||||
const tagsSource = State.state.allElements.getEventSourceFor(feature);
|
const tagsSource = State.state.allElements.addOrGetElement(feature);
|
||||||
// Every object is tied to exactly one layer
|
// Every object is tied to exactly one layer
|
||||||
const layer = this._layerDict[feature._matching_layer_id];
|
const layer = this._layerDict[feature._matching_layer_id];
|
||||||
return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined);
|
return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined);
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": "amenity=public_bookcase"
|
"osmTags": "amenity=public_bookcase"
|
||||||
},
|
},
|
||||||
"calculatedTags": {
|
|
||||||
"_distanceToPietervdn": "feat.distanceTo(3.704388, 51.05281) < 1 ? 'closeby' : 'faraway'"
|
|
||||||
},
|
|
||||||
"minzoom": 12,
|
"minzoom": 12,
|
||||||
"wayHandling": 2,
|
"wayHandling": 2,
|
||||||
"title": {
|
"title": {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
"minzoom": 16,
|
"minzoom": 16,
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
"or": [
|
"or": [
|
||||||
"highway=pedestrian",
|
"highway=pedestrian",
|
||||||
"highway=footway",
|
"highway=footway",
|
||||||
|
@ -14,6 +16,10 @@
|
||||||
"highway=living_street",
|
"highway=living_street",
|
||||||
"highway=track"
|
"highway=track"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"access!=no",
|
||||||
|
"access!=private"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
|
@ -51,7 +57,8 @@
|
||||||
"nl": "Woonerf"
|
"nl": "Woonerf"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{"if": "highway=path",
|
{
|
||||||
|
"if": "highway=path",
|
||||||
"then": "Klein pad"
|
"then": "Klein pad"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,8 +4,8 @@ import SpecialVisualizations from "../UI/SpecialVisualizations";
|
||||||
import {writeFileSync} from "fs";
|
import {writeFileSync} from "fs";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
|
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
|
||||||
import {ExtraFunction} from "../Logic/MetaTagging";
|
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
|
import {ExtraFunction} from "../Logic/ExtraFunction";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue