Add switch to hide certain features, cleanup of code

This commit is contained in:
Pieter Vander Vennet 2021-03-25 15:19:44 +01:00
parent aa0989b72a
commit 1b1ec9f15d
18 changed files with 230 additions and 173 deletions

View file

@ -43,7 +43,7 @@ class TitleElement extends UIElement {
}
if (layer.source.osmTags.matchesProperties(properties)) {
const title = new TagRenderingAnswer(
this._allElementsStorage.getEventSourceFor(feature),
this._allElementsStorage.addOrGetElement(feature),
layer.title
)
return new Combine([defaultTitle, " | ", title]).Render();

View file

@ -15,30 +15,42 @@ export class ElementStorage {
this._elements[id] = eventSource;
}
addElement(element): UIEventSource<any> {
const eventSource = new UIEventSource<any>(element.properties, "tags of "+element.properties.id);
this._elements[element.properties.id] = eventSource;
return eventSource;
}
addOrGetElement(element: any) : UIEventSource<any>{
const elementId = element.properties.id;
/**
* Creates a UIEventSource for the tags of the given feature.
* If an UIEventsource has been created previously, the same UIEventSource will be returned
*
* Note: it will cleverly merge the tags, if needed
*/
addOrGetElement(feature: any): UIEventSource<any> {
const elementId = feature.properties.id;
if (elementId in this._elements) {
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;
// The element already exists
// We add all the new keys to the old keys
for (const k in element.properties) {
const v = element.properties[k];
let somethingChanged = false;
for (const k in feature.properties) {
const v = feature.properties[k];
if (keptKeys[k] !== v) {
keptKeys[k] = v;
es.ping();
somethingChanged = true;
}
}
if (somethingChanged) {
es.ping();
}
return es;
}else{
return this.addElement(element);
} else {
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);
}
getEventSourceFor(feature): UIEventSource<any> {
return this.getEventSourceById(feature.properties.id);
}
}

81
Logic/ExtraFunction.ts Normal file
View 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>
`
}

View file

@ -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)
* 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 {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;

View file

@ -12,6 +12,7 @@ import LocalStorageSource from "./LocalStorageSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Loc from "../../Models/Loc";
import GeoJsonSource from "./GeoJsonSource";
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
export default class FeaturePipeline implements FeatureSource {
@ -25,42 +26,42 @@ export default class FeaturePipeline implements FeatureSource {
const amendedOverpassSource =
new RememberingSource(
new WayHandlingApplyingFeatureSource(flayers,
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers,
new LocalStorageSaver(updater, layout)))
)
new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers,
new LocalStorageSaver(updater, layout)))
);
const geojsonSources: GeoJsonSource [] = []
for (const flayer of flayers.data) {
const sourceUrl = flayer.layerDef.source.geojsonSource
if (sourceUrl !== undefined) {
geojsonSources.push(new WayHandlingApplyingFeatureSource(flayers,
new GeoJsonSource(flayer.layerDef.id, sourceUrl)))
geojsonSources.push(
new GeoJsonSource(flayer.layerDef.id, sourceUrl))
}
}
const amendedLocalStorageSource =
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);
const merged = new FeatureSourceMerger([
amendedOverpassSource,
amendedLocalStorageSource,
newPoints,
...geojsonSources
]);
const merged =
new MetaTaggingFeatureSource(
new FeatureSourceMerger([
amendedOverpassSource,
amendedLocalStorageSource,
newPoints,
...geojsonSources
]));
const source =
new FilteringFeatureSource(
flayers,
locationControl,
merged
);
new WayHandlingApplyingFeatureSource(flayers,
new FilteringFeatureSource(
flayers,
locationControl,
merged
));
this.features = source.features;
}

View file

@ -29,6 +29,7 @@ export default class FilteringFeatureSource implements FeatureSource {
const newFeatures = features.filter(f => {
const layerId = f.feature._matching_layer_id;
if (layerId !== undefined) {
const layer: {
isDisplayed: UIEventSource<boolean>,
@ -38,6 +39,17 @@ export default class FilteringFeatureSource implements FeatureSource {
console.error("No layer found with id ", layerId);
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)) {
return true;
}

View 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);
});
}
}

View file

@ -3,6 +3,9 @@ import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
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 {
features: UIEventSource<{ feature: any; freshness: Date }[]>;
@ -46,6 +49,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource {
continue;
}
// Create the copy
const centerPoint = GeoOperations.centerpoint(feat);
centerPoint._matching_layer_id = feat._matching_layer_id;
newFeatures.push({feature: centerPoint, freshness: f.freshness});

View file

@ -1,85 +1,6 @@
import {GeoOperations} from "./GeoOperations";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import SimpleMetaTagger from "./SimpleMetaTagger";
import {UIElement} from "../UI/UIElement";
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);
}
}
import {ExtraFunction} from "./ExtraFunction";
/**
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
@ -163,4 +84,6 @@ export default class MetaTagging {
}
}
}
}

View file

@ -7,30 +7,15 @@ import {TagsFilter} from "../TagsFilter";
* Interfaces overpass to get all the latest data
*/
export class Overpass {
private _filter: TagsFilter
public static testUrl: string = null
private readonly _extraScripts: string[];
private _filter: TagsFilter
private readonly _extraScripts: string[];
constructor(filter: TagsFilter, extraScripts: string[]) {
this._filter = filter
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 {
let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
@ -52,11 +37,27 @@ export class Overpass {
onFail("Runtime error (timeout)")
return;
}
// @ts-ignore
const geojson = OsmToGeoJson.default(json);
console.log("Received geojson", geojson)
const osmTime = new Date(json.osm3s.timestamp_osm_base);
continuation(geojson, osmTime);
}).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)
}
}

View file

@ -64,7 +64,7 @@ export default class SimpleMetaTagger {
SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => {
try {
feature.properties["_country"] = countries[0].trim().toLowerCase();
const tagsSource = State.state.allElements.getEventSourceFor(feature);
const tagsSource = State.state.allElements.addOrGetElement(feature);
tagsSource.ping();
} catch (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')",
(feature => {
const tagsSource = State.state.allElements.getEventSourceFor(feature);
const tagsSource = State.state.allElements.addOrGetElement(feature);
tagsSource.addCallbackAndRun(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) {
return;