Skip to content

Commit b14ca14

Browse files
committed
Convert contour layer to geojson
1 parent 38724f9 commit b14ca14

3 files changed

Lines changed: 142 additions & 185 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.kylecorry.trail_sense.shared.dem.map_layers
2+
3+
import com.kylecorry.andromeda.core.cache.AppServiceRegistry
4+
import com.kylecorry.andromeda.core.math.DecimalFormatter
5+
import com.kylecorry.andromeda.geojson.GeoJsonFeature
6+
import com.kylecorry.andromeda.geojson.GeoJsonFeatureCollection
7+
import com.kylecorry.andromeda.geojson.GeoJsonObject
8+
import com.kylecorry.sol.math.SolMath
9+
import com.kylecorry.sol.science.geology.CoordinateBounds
10+
import com.kylecorry.sol.units.Distance
11+
import com.kylecorry.trail_sense.shared.UserPreferences
12+
import com.kylecorry.trail_sense.shared.dem.DEM
13+
import com.kylecorry.trail_sense.shared.dem.colors.ElevationColorMap
14+
import com.kylecorry.trail_sense.shared.dem.colors.TrailSenseVibrantElevationColorMap
15+
import com.kylecorry.trail_sense.shared.extensions.lineString
16+
import com.kylecorry.trail_sense.shared.map_layers.tiles.TileMath
17+
import com.kylecorry.trail_sense.shared.map_layers.ui.layers.geojson.sources.GeoJsonSource
18+
import com.kylecorry.trail_sense.tools.paths.domain.LineStyle
19+
20+
class ContourGeoJsonSource : GeoJsonSource {
21+
22+
private val units = AppServiceRegistry.get<UserPreferences>().baseDistanceUnits
23+
private val minZoomLevel = 13
24+
private val maxZoomLevel = 19
25+
26+
var colorScale: ElevationColorMap = TrailSenseVibrantElevationColorMap()
27+
28+
private val validIntervals by lazy {
29+
if (units.isMetric) {
30+
mapOf(
31+
13 to 50f,
32+
14 to 50f,
33+
15 to 50f,
34+
16 to 10f,
35+
17 to 10f,
36+
18 to 10f,
37+
19 to 10f
38+
)
39+
} else {
40+
mapOf(
41+
13 to Distance.Companion.feet(200f).meters().value,
42+
14 to Distance.Companion.feet(200f).meters().value,
43+
15 to Distance.Companion.feet(200f).meters().value,
44+
16 to Distance.Companion.feet(40f).meters().value,
45+
17 to Distance.Companion.feet(40f).meters().value,
46+
18 to Distance.Companion.feet(40f).meters().value,
47+
19 to Distance.Companion.feet(40f).meters().value
48+
)
49+
}
50+
}
51+
52+
private val baseResolution = 1 / 240.0
53+
private val validResolutions = mapOf(
54+
13 to baseResolution,
55+
14 to baseResolution / 2,
56+
15 to baseResolution / 4,
57+
16 to baseResolution / 4,
58+
17 to baseResolution / 4,
59+
18 to baseResolution / 4,
60+
19 to baseResolution / 4
61+
)
62+
63+
private val showLabelsOnAllContoursZoomLevels = setOf(
64+
14, 15, 19
65+
)
66+
67+
override suspend fun load(
68+
bounds: CoordinateBounds,
69+
metersPerPixel: Float
70+
): GeoJsonObject? {
71+
val zoomLevel = TileMath.distancePerPixelToZoom(
72+
metersPerPixel.toDouble(),
73+
(bounds.north + bounds.south) / 2
74+
).coerceAtMost(maxZoomLevel)
75+
76+
if (zoomLevel < minZoomLevel) {
77+
return null
78+
}
79+
80+
val interval = validIntervals[zoomLevel] ?: validIntervals.values.first()
81+
val contours = DEM.getContourLines(bounds, interval, validResolutions[zoomLevel]!!)
82+
var i = -10000L
83+
84+
val features = contours.flatMap { level ->
85+
val isImportantLine = SolMath.isZero((level.elevation / interval) % 5, 0.1f)
86+
val name = DecimalFormatter.format(
87+
Distance.Companion.meters(level.elevation).convertTo(units).value, 0
88+
)
89+
val color = colorScale.getElevationColor(level.elevation)
90+
level.lines.map { line ->
91+
GeoJsonFeature.lineString(
92+
line,
93+
i++,
94+
name = if (isImportantLine || showLabelsOnAllContoursZoomLevels.contains(
95+
zoomLevel
96+
)
97+
) {
98+
name
99+
} else {
100+
null
101+
},
102+
color = color,
103+
lineStyle = LineStyle.Solid,
104+
thicknessScale = if (isImportantLine) {
105+
0.8f
106+
} else {
107+
0.4f
108+
}
109+
)
110+
}
111+
}
112+
113+
return GeoJsonFeatureCollection(features)
114+
}
115+
}
Lines changed: 8 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,19 @@
11
package com.kylecorry.trail_sense.shared.dem.map_layers
22

3-
import android.graphics.Color
4-
import com.kylecorry.andromeda.canvas.ICanvasDrawer
5-
import com.kylecorry.andromeda.core.cache.AppServiceRegistry
6-
import com.kylecorry.andromeda.core.math.DecimalFormatter
7-
import com.kylecorry.andromeda.core.units.PixelCoordinate
8-
import com.kylecorry.sol.math.SolMath
9-
import com.kylecorry.sol.units.Distance
10-
import com.kylecorry.trail_sense.main.errors.SafeMode
11-
import com.kylecorry.trail_sense.shared.UserPreferences
12-
import com.kylecorry.trail_sense.shared.dem.Contour
13-
import com.kylecorry.trail_sense.shared.dem.DEM
14-
import com.kylecorry.trail_sense.shared.dem.colors.ElevationColorMap
153
import com.kylecorry.trail_sense.shared.dem.colors.ElevationColorMapFactory
16-
import com.kylecorry.trail_sense.shared.dem.colors.TrailSenseVibrantElevationColorMap
174
import com.kylecorry.trail_sense.shared.map_layers.MapLayerBackgroundTask
18-
import com.kylecorry.trail_sense.shared.map_layers.tiles.TileMath
19-
import com.kylecorry.trail_sense.shared.map_layers.ui.layers.IAsyncLayer
20-
import com.kylecorry.trail_sense.shared.map_layers.ui.layers.IMapView
21-
import com.kylecorry.trail_sense.tools.navigation.ui.MappableLocation
22-
import com.kylecorry.trail_sense.tools.navigation.ui.MappablePath
23-
import com.kylecorry.trail_sense.tools.paths.domain.LineStyle
24-
import com.kylecorry.trail_sense.tools.paths.map_layers.PathLayer
5+
import com.kylecorry.trail_sense.shared.map_layers.ui.layers.geojson.GeoJsonLayer
256

26-
class ContourLayer(private val taskRunner: MapLayerBackgroundTask = MapLayerBackgroundTask()) :
27-
IAsyncLayer {
28-
29-
private val pathLayer = PathLayer()
30-
31-
init {
32-
taskRunner.addTask { bounds, metersPerPixel ->
33-
val zoomLevel = TileMath.distancePerPixelToZoom(
34-
metersPerPixel.toDouble(),
35-
(bounds.north + bounds.south) / 2
36-
).coerceAtMost(maxZoomLevel)
37-
38-
if (zoomLevel < minZoomLevel) {
39-
contours = emptyList()
40-
return@addTask
41-
}
42-
43-
val interval = validIntervals[zoomLevel] ?: validIntervals.values.first()
44-
contours = DEM.getContourLines(bounds, interval, validResolutions[zoomLevel]!!)
45-
contourInterval = interval
46-
lastZoomLevel = zoomLevel
47-
var i = -10000L
48-
pathLayer.setPaths(contours.flatMap { level ->
49-
val isImportantLine = SolMath.isZero((level.elevation / contourInterval) % 5, 0.1f)
50-
val name = DecimalFormatter.format(
51-
Distance.Companion.meters(level.elevation).convertTo(units).value, 0
52-
)
53-
val color = colorScale.getElevationColor(level.elevation)
54-
level.lines.map { line ->
55-
MappablePath(
56-
i++,
57-
line.map {
58-
MappableLocation(0, it, Color.TRANSPARENT, null)
59-
},
60-
color,
61-
LineStyle.Solid,
62-
name = if (isImportantLine || showLabelsOnAllContoursZoomLevels.contains(
63-
lastZoomLevel
64-
)
65-
) {
66-
name
67-
} else {
68-
null
69-
},
70-
thicknessScale = if (isImportantLine) {
71-
0.8f
72-
} else {
73-
0.4f
74-
}
75-
)
76-
}
77-
78-
})
79-
}
80-
}
81-
82-
private val units by lazy { AppServiceRegistry.get<UserPreferences>().baseDistanceUnits }
83-
84-
private val minZoomLevel = 13
85-
private val maxZoomLevel = 19
7+
class ContourLayer(taskRunner: MapLayerBackgroundTask = MapLayerBackgroundTask()) :
8+
GeoJsonLayer<ContourGeoJsonSource>(ContourGeoJsonSource(), taskRunner) {
869

8710
fun setPreferences(prefs: ContourMapLayerPreferences) {
88-
_percentOpacity = prefs.opacity.get() / 100f
89-
pathLayer.setShouldRenderLabels(prefs.showLabels.get())
11+
percentOpacity = prefs.opacity.get() / 100f
12+
renderer.configureLineStringRenderer(shouldRenderLabels = prefs.showLabels.get())
9013
// TODO: More experimentation required before this is enabled for everyone
91-
// pathLayer.setShouldRenderSmoothPaths(isDebug())
92-
colorScale = ElevationColorMapFactory().getElevationColorMap(prefs.colorStrategy.get())
14+
// renderer.configureLineStringRenderer(shouldRenderSmoothPaths = isDebug())
15+
source.colorScale =
16+
ElevationColorMapFactory().getElevationColorMap(prefs.colorStrategy.get())
9317
invalidate()
9418
}
95-
96-
private var colorScale: ElevationColorMap = TrailSenseVibrantElevationColorMap()
97-
98-
private val validIntervals by lazy {
99-
if (units.isMetric) {
100-
mapOf(
101-
13 to 50f,
102-
14 to 50f,
103-
15 to 50f,
104-
16 to 10f,
105-
17 to 10f,
106-
18 to 10f,
107-
19 to 10f
108-
)
109-
} else {
110-
mapOf(
111-
13 to Distance.Companion.feet(200f).meters().value,
112-
14 to Distance.Companion.feet(200f).meters().value,
113-
15 to Distance.Companion.feet(200f).meters().value,
114-
16 to Distance.Companion.feet(40f).meters().value,
115-
17 to Distance.Companion.feet(40f).meters().value,
116-
18 to Distance.Companion.feet(40f).meters().value,
117-
19 to Distance.Companion.feet(40f).meters().value
118-
)
119-
}
120-
}
121-
122-
private val baseResolution = 1 / 240.0
123-
private val validResolutions = mapOf(
124-
13 to baseResolution,
125-
14 to baseResolution / 2,
126-
15 to baseResolution / 4,
127-
16 to baseResolution / 4,
128-
17 to baseResolution / 4,
129-
18 to baseResolution / 4,
130-
19 to baseResolution / 4
131-
)
132-
133-
private val showLabelsOnAllContoursZoomLevels = setOf(
134-
14, 15, 19
135-
)
136-
137-
private var contours = listOf<Contour>()
138-
private var contourInterval = 1f
139-
private var lastZoomLevel = -1
140-
141-
override fun draw(
142-
drawer: ICanvasDrawer,
143-
map: IMapView
144-
) {
145-
if (SafeMode.isEnabled() || map.metersPerPixel > 75f) {
146-
return
147-
}
148-
149-
taskRunner.scheduleUpdate(map.mapBounds, map.metersPerPixel)
150-
pathLayer.draw(drawer, map)
151-
}
152-
153-
override fun drawOverlay(
154-
drawer: ICanvasDrawer,
155-
map: IMapView
156-
) {
157-
pathLayer.drawOverlay(drawer, map)
158-
}
159-
160-
override fun invalidate() {
161-
pathLayer.invalidate()
162-
}
163-
164-
override fun onClick(
165-
drawer: ICanvasDrawer,
166-
map: IMapView,
167-
pixel: PixelCoordinate
168-
): Boolean {
169-
return false
170-
}
171-
172-
private var _percentOpacity: Float = 1f
173-
174-
override val percentOpacity: Float
175-
get() = _percentOpacity
176-
177-
override fun setHasUpdateListener(listener: (() -> Unit)?) {
178-
pathLayer.setHasUpdateListener(listener)
179-
}
18019
}

app/src/main/java/com/kylecorry/trail_sense/shared/map_layers/ui/layers/geojson/GeoJsonLayer.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,29 @@ import com.kylecorry.trail_sense.shared.map_layers.ui.layers.IMapView
1010
import com.kylecorry.trail_sense.shared.map_layers.ui.layers.geojson.sources.GeoJsonSource
1111
import kotlinx.coroutines.CancellationException
1212

13-
open class GeoJsonLayer<T : GeoJsonSource>(protected val source: T) : IAsyncLayer {
13+
open class GeoJsonLayer<T : GeoJsonSource>(
14+
protected val source: T,
15+
private val taskRunner: MapLayerBackgroundTask = MapLayerBackgroundTask()
16+
) : IAsyncLayer {
1417

15-
private val renderer = GeoJsonRenderer()
16-
private val taskRunner = MapLayerBackgroundTask()
18+
protected val renderer = GeoJsonRenderer()
1719
private var isInvalid = true
1820

1921
init {
2022
renderer.setOnClickListener(this::onClick)
23+
taskRunner.addTask { bounds, metersPerPixel ->
24+
isInvalid = false
25+
try {
26+
val obj =
27+
source.load(bounds, metersPerPixel) ?: GeoJsonFeatureCollection(emptyList())
28+
renderer.setGeoJsonObject(obj)
29+
} catch (e: CancellationException) {
30+
throw e
31+
} catch (e: Throwable) {
32+
e.printStackTrace()
33+
isInvalid = true
34+
}
35+
}
2136
}
2237

2338
override fun setHasUpdateListener(listener: (() -> Unit)?) {
@@ -33,19 +48,7 @@ open class GeoJsonLayer<T : GeoJsonSource>(protected val source: T) : IAsyncLaye
3348
map.mapBounds,
3449
map.metersPerPixel,
3550
isInvalid
36-
) { bounds, metersPerPixel ->
37-
isInvalid = false
38-
try {
39-
val obj =
40-
source.load(bounds, metersPerPixel) ?: GeoJsonFeatureCollection(emptyList())
41-
renderer.setGeoJsonObject(obj)
42-
} catch (e: CancellationException) {
43-
throw e
44-
} catch (e: Throwable) {
45-
e.printStackTrace()
46-
isInvalid = true
47-
}
48-
}
51+
)
4952
}
5053

5154
override fun drawOverlay(

0 commit comments

Comments
 (0)