Add abstraction for coordinate projections
This commit is contained in:
parent
cda8d8e76f
commit
42e0a4c46d
@ -27,7 +27,8 @@ public interface DraggableMapFeature : MapFeature {
|
|||||||
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Double): GmcRectangle? =
|
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Double): GmcRectangle? =
|
||||||
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||||
|
|
||||||
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
|
internal fun Pair<Number, Number>.toCoordinates() =
|
||||||
|
GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
||||||
|
|
||||||
internal val defaultZoomRange = 1..18
|
internal val defaultZoomRange = 1..18
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ public class MapCircleFeature(
|
|||||||
) : DraggableMapFeature {
|
) : DraggableMapFeature {
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||||
return GmcRectangle.square(center, (size/scale).radians, (size/scale).radians)
|
return GmcRectangle.square(center, (size / scale).radians, (size / scale).radians)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||||
@ -91,7 +92,7 @@ public class MapRectangleFeature(
|
|||||||
) : DraggableMapFeature {
|
) : DraggableMapFeature {
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||||
return GmcRectangle.square(center, (size.height.value/scale).radians, (size.width.value/scale).radians)
|
return GmcRectangle.square(center, (size.height.value / scale).radians, (size.width.value / scale).radians)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||||
@ -134,11 +135,11 @@ public class MapVectorImageFeature(
|
|||||||
public val painter: Painter,
|
public val painter: Painter,
|
||||||
public val size: DpSize,
|
public val size: DpSize,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
) : DraggableMapFeature{
|
) : DraggableMapFeature {
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||||
MapVectorImageFeature(newCoordinates,painter, size, zoomRange)
|
MapVectorImageFeature(newCoordinates, painter, size, zoomRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -166,7 +167,7 @@ public class MapTextFeature(
|
|||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
public val color: Color,
|
public val color: Color,
|
||||||
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
||||||
) : DraggableMapFeature{
|
) : DraggableMapFeature {
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||||
|
@ -171,6 +171,16 @@ public fun MapFeatureBuilder.points(
|
|||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
): FeatureId = addFeature(id, MapPointsFeature(points, zoomRange, stroke, color, pointMode))
|
): FeatureId = addFeature(id, MapPointsFeature(points, zoomRange, stroke, color, pointMode))
|
||||||
|
|
||||||
|
@JvmName("pointsFromPairs")
|
||||||
|
public fun MapFeatureBuilder.points(
|
||||||
|
points: List<Pair<Double, Double>>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
stroke: Float = 2f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
pointMode: PointMode = PointMode.Points,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun MapFeatureBuilder.image(
|
public fun MapFeatureBuilder.image(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
|
@ -10,7 +10,16 @@ import kotlin.math.atan
|
|||||||
import kotlin.math.ln
|
import kotlin.math.ln
|
||||||
import kotlin.math.sinh
|
import kotlin.math.sinh
|
||||||
|
|
||||||
public data class MercatorCoordinates(val x: Distance, val y: Distance)
|
public data class ProjectionCoordinates(val x: Distance, val y: Distance)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T the type of projection coordinates
|
||||||
|
*/
|
||||||
|
public interface MapProjection<T: Any>{
|
||||||
|
public fun toGeodetic(pc: T): GeodeticMapCoordinates
|
||||||
|
public fun toProjection(gmc: GeodeticMapCoordinates): T
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param baseLongitude the longitude offset in radians
|
* @param baseLongitude the longitude offset in radians
|
||||||
@ -21,18 +30,18 @@ public open class MercatorProjection(
|
|||||||
public val baseLongitude: Angle = Angle.zero,
|
public val baseLongitude: Angle = Angle.zero,
|
||||||
protected val radius: Distance = DEFAULT_EARTH_RADIUS,
|
protected val radius: Distance = DEFAULT_EARTH_RADIUS,
|
||||||
private val correctedRadius: ((GeodeticMapCoordinates) -> Distance)? = null,
|
private val correctedRadius: ((GeodeticMapCoordinates) -> Distance)? = null,
|
||||||
) {
|
): MapProjection<ProjectionCoordinates> {
|
||||||
|
|
||||||
public fun toGeodetic(mc: MercatorCoordinates): GeodeticMapCoordinates {
|
override fun toGeodetic(pc: ProjectionCoordinates): GeodeticMapCoordinates {
|
||||||
val res = GeodeticMapCoordinates.ofRadians(
|
val res = GeodeticMapCoordinates.ofRadians(
|
||||||
atan(sinh(mc.y / radius)),
|
atan(sinh(pc.y / radius)),
|
||||||
baseLongitude.radians.value + (mc.x / radius),
|
baseLongitude.radians.value + (pc.x / radius),
|
||||||
)
|
)
|
||||||
return if (correctedRadius != null) {
|
return if (correctedRadius != null) {
|
||||||
val r = correctedRadius.invoke(res)
|
val r = correctedRadius.invoke(res)
|
||||||
GeodeticMapCoordinates.ofRadians(
|
GeodeticMapCoordinates.ofRadians(
|
||||||
atan(sinh(mc.y / r)),
|
atan(sinh(pc.y / r)),
|
||||||
baseLongitude.radians.value + mc.x / r,
|
baseLongitude.radians.value + pc.x / r,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res
|
res
|
||||||
@ -42,10 +51,10 @@ public open class MercatorProjection(
|
|||||||
/**
|
/**
|
||||||
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
|
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
|
||||||
*/
|
*/
|
||||||
public fun toMercator(gmc: GeodeticMapCoordinates): MercatorCoordinates {
|
override fun toProjection(gmc: GeodeticMapCoordinates): ProjectionCoordinates {
|
||||||
require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
|
require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
|
||||||
val r: Distance = correctedRadius?.invoke(gmc) ?: radius
|
val r: Distance = correctedRadius?.invoke(gmc) ?: radius
|
||||||
return MercatorCoordinates(
|
return ProjectionCoordinates(
|
||||||
x = r * (gmc.longitude - baseLongitude).radians.value,
|
x = r * (gmc.longitude - baseLongitude).radians.value,
|
||||||
y = r * ln(tan(pi / 4 + gmc.latitude / 2))
|
y = r * ln(tan(pi / 4 + gmc.latitude / 2))
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user