2022-01-19 20:34:04 +01:00
import { BBox } from "../../Logic/BBox" ;
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" ;
import Combine from "../Base/Combine" ;
import Title from "../Base/Title" ;
import { Overpass } from "../../Logic/Osm/Overpass" ;
import { UIEventSource } from "../../Logic/UIEventSource" ;
import Constants from "../../Models/Constants" ;
import RelationsTracker from "../../Logic/Osm/RelationsTracker" ;
import { VariableUiElement } from "../Base/VariableUIElement" ;
import { FixedUiElement } from "../Base/FixedUiElement" ;
import { FlowStep } from "./FlowStep" ;
import Loading from "../Base/Loading" ;
import { SubtleButton } from "../Base/SubtleButton" ;
import Svg from "../../Svg" ;
import { Utils } from "../../Utils" ;
import { IdbLocalStorage } from "../../Logic/Web/IdbLocalStorage" ;
import Minimap from "../Base/Minimap" ;
import BaseLayer from "../../Models/BaseLayer" ;
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" ;
import Loc from "../../Models/Loc" ;
import Attribution from "../BigComponents/Attribution" ;
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" ;
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" ;
import ValidatedTextField from "../Input/ValidatedTextField" ;
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" ;
import * as currentview from "../../assets/layers/current_view/current_view.json"
import * as import_candidate from "../../assets/layers/import_candidate/import_candidate.json"
import { GeoOperations } from "../../Logic/GeoOperations" ;
import FeatureInfoBox from "../Popup/FeatureInfoBox" ;
2022-01-21 01:57:16 +01:00
import { ImportUtils } from "./ImportUtils" ;
2022-01-19 20:34:04 +01:00
/ * *
* Given the data to import , the bbox and the layer , will query overpass for similar items
* /
2022-03-24 03:11:29 +01:00
export default class ConflationChecker extends Combine implements FlowStep < { features : any [ ] , theme : string } > {
2022-01-19 20:34:04 +01:00
public readonly IsValid
2022-04-06 04:21:15 +02:00
public readonly Value : UIEventSource < { features : any [ ] , theme : string } >
2022-01-19 20:34:04 +01:00
constructor (
state ,
2022-03-24 03:11:29 +01:00
params : { bbox : BBox , layer : LayerConfig , theme : string , features : any [ ] } ) {
2022-01-26 21:40:38 +01:00
2022-01-19 20:34:04 +01:00
const bbox = params . bbox . padAbsolute ( 0.0001 )
const layer = params . layer ;
2022-03-24 03:11:29 +01:00
const toImport : { features : any [ ] } = params ;
2022-01-26 21:40:38 +01:00
let overpassStatus = new UIEventSource < { error : string } | "running" | "success" | "idle" | "cached" > ( "idle" )
2022-01-21 01:57:16 +01:00
const cacheAge = new UIEventSource < number > ( undefined ) ;
2022-01-19 20:34:04 +01:00
const fromLocalStorage = IdbLocalStorage . Get < [ any , Date ] > ( "importer-overpass-cache-" + layer . id , {
2022-03-24 03:11:29 +01:00
2022-01-19 20:34:04 +01:00
whenLoaded : ( v ) = > {
2022-03-24 03:11:29 +01:00
if ( v !== undefined && v !== null ) {
2022-01-19 20:34:04 +01:00
console . log ( "Loaded from local storage:" , v )
const [ geojson , date ] = v ;
const timeDiff = ( new Date ( ) . getTime ( ) - date . getTime ( ) ) / 1000 ;
2022-01-26 21:40:38 +01:00
console . log ( "Loaded " , geojson . features . length , " features; cache is " , timeDiff , "seconds old" )
2022-01-21 01:57:16 +01:00
cacheAge . setData ( timeDiff )
2022-01-19 20:34:04 +01:00
if ( timeDiff < 24 * 60 * 60 ) {
// Recently cached!
overpassStatus . setData ( "cached" )
return ;
}
2022-01-21 01:57:16 +01:00
cacheAge . setData ( - 1 )
2022-01-19 20:34:04 +01:00
}
// Load the data!
const url = Constants . defaultOverpassUrls [ 1 ]
const relationTracker = new RelationsTracker ( )
const overpass = new Overpass ( params . layer . source . osmTags , [ ] , url , new UIEventSource < number > ( 180 ) , relationTracker , true )
console . log ( "Loading from overpass!" )
overpassStatus . setData ( "running" )
overpass . queryGeoJson ( bbox ) . then (
2022-01-26 21:40:38 +01:00
( [ data , date ] ) = > {
console . log ( "Received overpass-data: " , data . features . length , "features are loaded at " , date ) ;
2022-01-19 20:34:04 +01:00
overpassStatus . setData ( "success" )
fromLocalStorage . setData ( [ data , date ] )
2022-01-26 21:40:38 +01:00
} ,
( error ) = > {
overpassStatus . setData ( { error } )
} )
2022-01-19 20:34:04 +01:00
}
} ) ;
2022-01-26 21:40:38 +01:00
const geojson : UIEventSource < any > = fromLocalStorage . map ( d = > {
2022-01-19 20:34:04 +01:00
if ( d === undefined ) {
return undefined
}
return d [ 0 ]
} )
const background = new UIEventSource < BaseLayer > ( AvailableBaseLayers . osmCarto )
const location = new UIEventSource < Loc > ( { lat : 0 , lon : 0 , zoom : 1 } )
const currentBounds = new UIEventSource < BBox > ( undefined )
2022-02-12 02:53:41 +01:00
const zoomLevel = ValidatedTextField . ForType ( "pnat" ) . ConstructInputElement ( )
2022-01-19 20:34:04 +01:00
zoomLevel . SetClass ( "ml-1 border border-black" )
zoomLevel . GetValue ( ) . syncWith ( LocalStorageSource . Get ( "importer-zoom-level" , "14" ) , true )
const osmLiveData = Minimap . createMiniMap ( {
allowMoving : true ,
location ,
background ,
bounds : currentBounds ,
attribution : new Attribution ( location , state . osmConnection . userDetails , undefined , currentBounds )
} )
osmLiveData . SetClass ( "w-full" ) . SetStyle ( "height: 500px" )
const preview = new StaticFeatureSource ( geojson . map ( geojson = > {
2022-01-26 21:40:38 +01:00
if ( geojson ? . features === undefined ) {
2022-01-19 20:34:04 +01:00
return [ ]
}
const zoomedEnough : boolean = osmLiveData . location . data . zoom >= Number ( zoomLevel . GetValue ( ) . data )
2022-01-26 21:40:38 +01:00
if ( ! zoomedEnough ) {
2022-01-19 20:34:04 +01:00
return [ ]
}
const bounds = osmLiveData . bounds . data
return geojson . features . filter ( f = > BBox . get ( f ) . overlapsWith ( bounds ) )
} , [ osmLiveData . bounds , zoomLevel . GetValue ( ) ] ) , false ) ;
2022-01-26 21:40:38 +01:00
2022-01-19 20:34:04 +01:00
new ShowDataLayer ( {
2022-01-26 21:40:38 +01:00
layerToShow : new LayerConfig ( currentview ) ,
2022-01-19 20:34:04 +01:00
state ,
leafletMap : osmLiveData.leafletMap ,
2022-01-21 01:57:16 +01:00
popup : undefined ,
2022-01-19 20:34:04 +01:00
zoomToFeatures : true ,
features : new StaticFeatureSource ( [
bbox . asGeoJson ( { } )
] , false )
} )
new ShowDataLayer ( {
2022-01-26 21:40:38 +01:00
layerToShow : layer ,
2022-01-19 20:34:04 +01:00
state ,
leafletMap : osmLiveData.leafletMap ,
popup : ( tags , layer ) = > new FeatureInfoBox ( tags , layer , state ) ,
zoomToFeatures : false ,
features : preview
} )
new ShowDataLayer ( {
2022-01-26 21:40:38 +01:00
layerToShow : new LayerConfig ( import_candidate ) ,
2022-01-19 20:34:04 +01:00
state ,
leafletMap : osmLiveData.leafletMap ,
popup : ( tags , layer ) = > new FeatureInfoBox ( tags , layer , state ) ,
zoomToFeatures : false ,
features : new StaticFeatureSource ( toImport . features , false )
} )
2022-01-26 21:40:38 +01:00
2022-02-12 02:53:41 +01:00
const nearbyCutoff = ValidatedTextField . ForType ( "pnat" ) . ConstructInputElement ( )
2022-01-19 20:34:04 +01:00
nearbyCutoff . SetClass ( "ml-1 border border-black" )
nearbyCutoff . GetValue ( ) . syncWith ( LocalStorageSource . Get ( "importer-cutoff" , "25" ) , true )
2022-01-26 21:40:38 +01:00
2022-01-19 20:34:04 +01:00
const matchedFeaturesMap = Minimap . createMiniMap ( {
allowMoving : true ,
background
} )
matchedFeaturesMap . SetClass ( "w-full" ) . SetStyle ( "height: 500px" )
// Featuresource showing OSM-features which are nearby a toImport-feature
const nearbyFeatures = new StaticFeatureSource ( geojson . map ( osmData = > {
2022-01-26 21:40:38 +01:00
if ( osmData ? . features === undefined ) {
2022-01-19 20:34:04 +01:00
return [ ]
}
const maxDist = Number ( nearbyCutoff . GetValue ( ) . data )
2022-01-26 21:40:38 +01:00
return osmData . features . filter ( f = >
toImport . features . some ( imp = >
maxDist >= GeoOperations . distanceBetween ( imp . geometry . coordinates , GeoOperations . centerpointCoordinates ( f ) ) ) )
2022-01-19 20:34:04 +01:00
} , [ nearbyCutoff . GetValue ( ) ] ) , false ) ;
2022-01-21 01:57:16 +01:00
const paritionedImport = ImportUtils . partitionFeaturesIfNearby ( toImport , geojson , nearbyCutoff . GetValue ( ) . map ( Number ) ) ;
2022-01-19 20:34:04 +01:00
// Featuresource showing OSM-features which are nearby a toImport-feature
2022-01-26 21:40:38 +01:00
const toImportWithNearby = new StaticFeatureSource ( paritionedImport . map ( els = > els ? . hasNearby ? ? [ ] ) , false ) ;
2022-01-19 20:34:04 +01:00
new ShowDataLayer ( {
2022-01-26 21:40:38 +01:00
layerToShow : layer ,
2022-01-19 20:34:04 +01:00
state ,
leafletMap : matchedFeaturesMap.leafletMap ,
popup : ( tags , layer ) = > new FeatureInfoBox ( tags , layer , state ) ,
zoomToFeatures : true ,
features : nearbyFeatures
} )
new ShowDataLayer ( {
2022-01-26 21:40:38 +01:00
layerToShow : new LayerConfig ( import_candidate ) ,
2022-01-19 20:34:04 +01:00
state ,
leafletMap : matchedFeaturesMap.leafletMap ,
popup : ( tags , layer ) = > new FeatureInfoBox ( tags , layer , state ) ,
zoomToFeatures : false ,
features : toImportWithNearby
} )
2022-01-26 21:40:38 +01:00
const conflationMaps = new Combine ( [
2022-01-21 01:57:16 +01:00
new VariableUiElement (
2022-01-26 21:40:38 +01:00
geojson . map ( geojson = > {
if ( geojson === undefined ) {
return undefined ;
}
return new SubtleButton ( Svg . download_svg ( ) , "Download the loaded geojson from overpass" ) . onClick ( ( ) = > {
Utils . offerContentsAsDownloadableFile ( JSON . stringify ( geojson , null , " " ) , "mapcomplete-" + layer . id + ".geojson" , {
mimetype : "application/json+geo"
} )
} ) ;
} ) ) ,
2022-01-21 01:57:16 +01:00
new VariableUiElement ( cacheAge . map ( age = > {
2022-01-26 21:40:38 +01:00
if ( age === undefined ) {
2022-01-21 01:57:16 +01:00
return undefined ;
}
2022-01-26 21:40:38 +01:00
if ( age < 0 ) {
2022-01-21 01:57:16 +01:00
return new FixedUiElement ( "Cache was expired" )
}
2022-01-26 21:40:38 +01:00
return new FixedUiElement ( "Loaded data is from the cache and is " + Utils . toHumanTime ( age ) + " old" )
2022-01-21 01:57:16 +01:00
} ) ) ,
new Title ( "Live data on OSM" ) ,
2022-03-24 03:11:29 +01:00
"The " + toImport . features . length + " red elements on the following map are all your import candidates." ,
new VariableUiElement ( geojson . map ( geojson = > new FixedUiElement ( ( geojson ? . features ? . length ? ? "No" ) + " elements are loaded from OpenStreetMap which match the layer " + layer . id + ". Zooming in might be needed to show them" ) ) ) ,
2022-01-21 01:57:16 +01:00
osmLiveData ,
2022-01-26 21:40:38 +01:00
new Combine ( [ "The live data is shown if the zoomlevel is at least " , zoomLevel , ". The current zoom level is " , new VariableUiElement ( osmLiveData . location . map ( l = > "" + l . zoom ) ) ] ) . SetClass ( "flex" ) ,
2022-01-21 01:57:16 +01:00
new Title ( "Nearby features" ) ,
2022-01-26 21:40:38 +01:00
new Combine ( [ "The following map shows features to import which have an OSM-feature within " , nearbyCutoff , "meter" ] ) . SetClass ( "flex" ) ,
2022-03-24 03:11:29 +01:00
new VariableUiElement ( toImportWithNearby . features . map ( feats = >
new FixedUiElement ( "The " + feats . length + " red elements on the following map will <b>not</b> be imported!" ) . SetClass ( "alert" ) ) ) ,
2022-01-21 01:57:16 +01:00
"Set the range to 0 or 1 if you want to import them all" ,
matchedFeaturesMap ] ) . SetClass ( "flex flex-col" )
2022-01-26 21:40:38 +01:00
2022-01-19 20:34:04 +01:00
super ( [
new Title ( "Comparison with existing data" ) ,
new VariableUiElement ( overpassStatus . map ( d = > {
if ( d === "idle" ) {
return new Loading ( "Checking local storage..." )
}
if ( d [ "error" ] !== undefined ) {
return new FixedUiElement ( "Could not load latest data from overpass: " + d [ "error" ] ) . SetClass ( "alert" )
}
2022-01-26 21:40:38 +01:00
if ( d === "running" ) {
2022-01-19 20:34:04 +01:00
return new Loading ( "Querying overpass..." )
}
2022-01-26 21:40:38 +01:00
if ( d === "cached" ) {
2022-01-21 01:57:16 +01:00
return conflationMaps
2022-01-19 20:34:04 +01:00
}
2022-01-26 21:40:38 +01:00
if ( d === "success" ) {
2022-01-21 01:57:16 +01:00
return conflationMaps
2022-01-19 20:34:04 +01:00
}
2022-01-26 21:40:38 +01:00
return new FixedUiElement ( "Unexpected state " + d ) . SetClass ( "alert" )
2022-01-21 01:57:16 +01:00
} ) )
2022-01-19 20:34:04 +01:00
] )
2022-04-06 04:21:15 +02:00
this . Value = paritionedImport . map ( feats = > ( { theme : params.theme , features : feats?.noNearby , layer : params.layer } ) )
2022-01-21 01:57:16 +01:00
this . IsValid = this . Value . map ( v = > v ? . features !== undefined && v . features . length > 0 )
}
2022-01-19 20:34:04 +01:00
}