Skip to content

Commit 6889e35

Browse files
committed
Improve KML models declaration.
1 parent 66c3b3b commit 6889e35

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1528
-281
lines changed

worldwind/src/commonMain/kotlin/earth/worldwind/formats/kml/KML.kt

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,16 @@
22

33
package earth.worldwind.formats.kml
44

5-
import earth.worldwind.formats.kml.models.AbstractStyle
6-
import earth.worldwind.formats.kml.models.Document
7-
import earth.worldwind.formats.kml.models.Feature
8-
import earth.worldwind.formats.kml.models.Folder
9-
import earth.worldwind.formats.kml.models.Geometry
10-
import earth.worldwind.formats.kml.models.GroundOverlay
11-
import earth.worldwind.formats.kml.models.IconStyle
12-
import earth.worldwind.formats.kml.models.LabelStyle
13-
import earth.worldwind.formats.kml.models.LineString
14-
import earth.worldwind.formats.kml.models.LineStyle
15-
import earth.worldwind.formats.kml.models.LinearRing
16-
import earth.worldwind.formats.kml.models.LookAt
17-
import earth.worldwind.formats.kml.models.MultiGeometry
18-
import earth.worldwind.formats.kml.models.Placemark
19-
import earth.worldwind.formats.kml.models.Point
20-
import earth.worldwind.formats.kml.models.PolyStyle
21-
import earth.worldwind.formats.kml.models.Polygon
22-
import earth.worldwind.formats.kml.models.Style
23-
import earth.worldwind.formats.kml.models.StyleMap
5+
import earth.worldwind.formats.kml.models.*
246
import kotlinx.coroutines.channels.ProducerScope
257
import kotlinx.coroutines.flow.channelFlow
268
import kotlinx.serialization.modules.SerializersModule
279
import kotlinx.serialization.modules.polymorphic
2810
import kotlinx.serialization.modules.subclass
29-
import nl.adaptivity.xmlutil.EventType
30-
import nl.adaptivity.xmlutil.XmlDeclMode
31-
import nl.adaptivity.xmlutil.XmlReader
32-
import nl.adaptivity.xmlutil.XmlUtilInternal
33-
import nl.adaptivity.xmlutil.attributes
11+
import nl.adaptivity.xmlutil.*
3412
import nl.adaptivity.xmlutil.core.XmlVersion
3513
import nl.adaptivity.xmlutil.core.impl.multiplatform.Reader
3614
import nl.adaptivity.xmlutil.serialization.XML
37-
import nl.adaptivity.xmlutil.xmlStreaming
3815
import kotlin.random.Random
3916

4017
@OptIn(XmlUtilInternal::class)
@@ -45,6 +22,7 @@ internal class KML {
4522
subclass(Document::class)
4623
subclass(Folder::class)
4724
subclass(Placemark::class)
25+
subclass(GroundOverlay::class)
4826
}
4927
polymorphic(Geometry::class) {
5028
subclass(LinearRing::class)
@@ -53,12 +31,24 @@ internal class KML {
5331
subclass(Point::class)
5432
subclass(Polygon::class)
5533
}
56-
polymorphic(AbstractStyle::class) {
34+
polymorphic(ColorStyle::class) {
5735
subclass(IconStyle::class)
5836
subclass(LabelStyle::class)
5937
subclass(LineStyle::class)
6038
subclass(PolyStyle::class)
6139
}
40+
polymorphic(AbstractView::class) {
41+
subclass(Camera::class)
42+
subclass(LookAt::class)
43+
}
44+
polymorphic(TimePrimitive::class) {
45+
subclass(TimeSpan::class)
46+
subclass(TimeStamp::class)
47+
}
48+
polymorphic(StyleSelector::class) {
49+
subclass(Style::class)
50+
subclass(StyleMap::class)
51+
}
6252
}
6353

6454
private val xml = XML(module) {

worldwind/src/commonMain/kotlin/earth/worldwind/formats/kml/KmlLayerFactory.kt

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ object KmlLayerFactory {
165165
if (styleMapId != null) {
166166
styleMaps[styleMapId] = styleMap
167167

168-
val styleId = styleMap.pairs?.firstOrNull()?.styleUrl?.removePrefix("#")
168+
val styleId = styleMap.pairs.firstOrNull()?.styleUrl?.removePrefix("#")
169169
val style = styles[styleId]?.also { styles[styleMapId] = it }
170170

171171
if (style != null) {
@@ -186,7 +186,7 @@ object KmlLayerFactory {
186186
val styleId = style.id
187187

188188
val finalId = styleMaps.entries.find { (_, value) ->
189-
value.pairs?.any { it.styleUrl?.removePrefix("#") == styleId } == true
189+
value.pairs.any { it.styleUrl.removePrefix("#") == styleId }
190190
}?.key ?: styleId
191191

192192
if (finalId != null) {
@@ -248,22 +248,16 @@ object KmlLayerFactory {
248248
}
249249
}
250250

251-
private fun LookAt.toGeomLookAt(): earth.worldwind.geom.LookAt? {
252-
val latitude = latitude ?: return null
253-
val longitude = longitude ?: return null
254-
val range = range ?: return null
255-
256-
return earth.worldwind.geom.LookAt(
257-
position = Position.fromDegrees(
258-
latitudeDegrees = latitude,
259-
longitudeDegrees = longitude,
260-
altitude = altitude ?: 0.0
261-
),
262-
altitudeMode = AltitudeMode.ABSOLUTE,
263-
range = range,
264-
heading = Angle.fromDegrees(heading ?: 0.0),
265-
tilt = Angle.fromDegrees(tilt ?: 0.0),
266-
roll = ZERO
267-
)
268-
}
251+
private fun LookAt.toGeomLookAt() = earth.worldwind.geom.LookAt(
252+
position = Position.fromDegrees(
253+
latitudeDegrees = latitude,
254+
longitudeDegrees = longitude,
255+
altitude = altitude
256+
),
257+
altitudeMode = AltitudeMode.ABSOLUTE,
258+
range = range,
259+
heading = Angle.fromDegrees(heading),
260+
tilt = Angle.fromDegrees(tilt),
261+
roll = ZERO
262+
)
269263
}

worldwind/src/commonMain/kotlin/earth/worldwind/formats/kml/KmlToRenderableConverter.kt

Lines changed: 34 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,19 @@ package earth.worldwind.formats.kml
22

33
import earth.worldwind.MR
44
import earth.worldwind.formats.*
5-
import earth.worldwind.formats.DEFAULT_DENSITY
6-
import earth.worldwind.formats.DEFAULT_FILL_COLOR
7-
import earth.worldwind.formats.DEFAULT_IMAGE_SCALE
8-
import earth.worldwind.formats.DEFAULT_LINE_COLOR
9-
import earth.worldwind.formats.HIGHLIGHT_INCREMENT
10-
import earth.worldwind.formats.forceHttps
11-
import earth.worldwind.formats.isValidHttpsUrl
12-
import earth.worldwind.formats.kml.models.Geometry
13-
import earth.worldwind.formats.kml.models.GroundOverlay
14-
import earth.worldwind.formats.kml.models.Icon
15-
import earth.worldwind.formats.kml.models.IconStyle
16-
import earth.worldwind.formats.kml.models.LabelStyle
17-
import earth.worldwind.formats.kml.models.LatLonBox
18-
import earth.worldwind.formats.kml.models.LineString
19-
import earth.worldwind.formats.kml.models.LineStyle
20-
import earth.worldwind.formats.kml.models.LinearRing
21-
import earth.worldwind.formats.kml.models.MultiGeometry
5+
import earth.worldwind.formats.kml.models.*
6+
import earth.worldwind.formats.kml.models.AltitudeMode as KMLAltitudeMode
227
import earth.worldwind.formats.kml.models.Placemark
23-
import earth.worldwind.formats.kml.models.Point
24-
import earth.worldwind.formats.kml.models.PolyStyle
258
import earth.worldwind.formats.kml.models.Polygon
26-
import earth.worldwind.formats.kml.models.Style
9+
import earth.worldwind.geom.*
2710
import earth.worldwind.geom.AltitudeMode
28-
import earth.worldwind.geom.Angle
29-
import earth.worldwind.geom.Offset
11+
import earth.worldwind.geom.Angle.Companion.degrees
3012
import earth.worldwind.geom.OffsetMode.FRACTION
3113
import earth.worldwind.geom.OffsetMode.PIXELS
32-
import earth.worldwind.geom.Position
33-
import earth.worldwind.geom.Sector
3414
import earth.worldwind.render.Color
3515
import earth.worldwind.render.Renderable
3616
import earth.worldwind.render.image.ImageSource
37-
import earth.worldwind.shape.AbstractShape
38-
import earth.worldwind.shape.Label
39-
import earth.worldwind.shape.Path
40-
import earth.worldwind.shape.PathType
41-
import earth.worldwind.shape.ShapeAttributes
42-
import earth.worldwind.shape.SurfaceImage
43-
import earth.worldwind.shape.TextAttributes
17+
import earth.worldwind.shape.*
4418

4519
internal class KmlToRenderableConverter {
4620

@@ -54,12 +28,12 @@ internal class KmlToRenderableConverter {
5428
/**
5529
* optimized method to get coordinates from a string
5630
*/
57-
private fun extractPoints(input: String?, altitudeOffset: Double? = 0.0): List<Position> {
31+
private fun extractPoints(input: String, altitudeOffset: Double = 0.0): List<Position> {
5832
// Normalize input by trimming leading/trailing whitespaces
5933
// and replacing all forms of space with a single space
60-
val normalizedInput = input?.trim()?.replace(spaceCharsRegex, " ")
34+
val normalizedInput = input.trim().replace(spaceCharsRegex, " ")
6135

62-
if (normalizedInput.isNullOrBlank()) return emptyList()
36+
if (normalizedInput.isBlank()) return emptyList()
6337

6438
val result = mutableListOf<Position>()
6539
val length = normalizedInput.length
@@ -107,7 +81,7 @@ internal class KmlToRenderableConverter {
10781
i++ // Skip the comma
10882
start = i
10983
while (i < length && normalizedInput[i] != ' ') i++
110-
alt = parseDouble(start, i) + (altitudeOffset ?: 0.0)
84+
alt = parseDouble(start, i) + altitudeOffset
11185
}
11286

11387
result.add(Position.fromDegrees(lat, lon, alt ?: 0.0))
@@ -137,7 +111,7 @@ internal class KmlToRenderableConverter {
137111
placemark: Placemark,
138112
definedStyle: Style? = null,
139113
): List<Renderable> {
140-
val style = placemark.stylesList?.firstOrNull() ?: definedStyle
114+
val style = placemark.styleSelector as? Style ?: definedStyle // TODO Add support of StyleMap
141115
val geometry: Geometry = placemark.geometryList?.firstOrNull() ?: return emptyList()
142116
return getRenderableFrom(geometry, style, placemark.name)
143117
}
@@ -186,7 +160,7 @@ internal class KmlToRenderableConverter {
186160
lineString: LineString,
187161
style: Style?,
188162
name: String?
189-
) = Path(extractPoints(lineString.coordinates?.value, lineString.altitudeOffset)).apply {
163+
) = Path(extractPoints(lineString.coordinates.value, lineString.altitudeOffset)).apply {
190164
altitudeMode = getAltitudeModeFrom(lineString.altitudeMode)
191165
isExtrude = lineString.extrude == true
192166
isFollowTerrain = lineString.tessellate == true
@@ -204,7 +178,7 @@ internal class KmlToRenderableConverter {
204178
linearRing: LinearRing,
205179
style: Style?,
206180
name: String?
207-
) = Path(extractPoints(linearRing.coordinates?.value, linearRing.altitudeOffset)).apply {
181+
) = Path(extractPoints(linearRing.coordinates.value, linearRing.altitudeOffset)).apply {
208182
altitudeMode = getAltitudeModeFrom(linearRing.altitudeMode)
209183
isExtrude = linearRing.extrude == true
210184
isFollowTerrain = linearRing.tessellate == true
@@ -232,18 +206,14 @@ internal class KmlToRenderableConverter {
232206
maximumIntermediatePoints = 0 // Disable intermediate point for performance reasons
233207

234208
polygon.outerBoundaryIs?.let {
235-
it.value?.forEach { linearRing ->
236-
linearRing.coordinates?.value?.let { value ->
237-
addBoundary(extractPoints(value, linearRing.altitudeOffset))
238-
}
209+
it.value.let { linearRing ->
210+
addBoundary(extractPoints(linearRing.coordinates.value, linearRing.altitudeOffset))
239211
}
240212
}
241213

242214
polygon.innerBoundaryIs?.let {
243-
it.value?.forEach { linearRing ->
244-
linearRing.coordinates?.value?.let { value ->
245-
addBoundary(extractPoints(value, linearRing.altitudeOffset))
246-
}
215+
it.value.forEach { linearRing ->
216+
addBoundary(extractPoints(linearRing.coordinates.value, linearRing.altitudeOffset))
247217
}
248218
}
249219

@@ -266,10 +236,10 @@ internal class KmlToRenderableConverter {
266236
}
267237

268238
private fun createPlacemark(point: Point, style: Style?, name: String?): Renderable? {
269-
val position = extractPoints(point.coordinates?.value).firstOrNull() ?: return null
239+
val position = extractPoints(point.coordinates.value).firstOrNull() ?: return null
270240

271-
val iconStyle = style?.stylesList?.filterIsInstance<IconStyle>()?.firstOrNull()
272-
val labelStyle = style?.stylesList?.filterIsInstance<LabelStyle>()?.firstOrNull()
241+
val iconStyle = style?.styles?.filterIsInstance<IconStyle>()?.firstOrNull()
242+
val labelStyle = style?.styles?.filterIsInstance<LabelStyle>()?.firstOrNull()
273243

274244
return if (iconStyle?.scale == 0.0) {
275245
Label(position, name).apply {
@@ -286,7 +256,8 @@ internal class KmlToRenderableConverter {
286256
imageSource = iconStyle?.icon?.toImageSource()?.also { imageScale *= density }
287257
?: ImageSource.fromResource(MR.images.kml_placemark) // Do not scale default placemark
288258
imageColor = iconStyle?.color?.let { fromHexABRG(it) } ?: defaultIconColor
289-
imageOffset = if (altitudeMode == AltitudeMode.CLAMP_TO_GROUND) Offset.bottomCenter() else Offset.center()
259+
imageOffset = iconStyle?.hotSpot?.let { Offset(getOffsetModeFrom(it.xunits), it.x, getOffsetModeFrom(it.yunits), it.y) }
260+
?: if (altitudeMode == AltitudeMode.CLAMP_TO_GROUND) Offset.bottomCenter() else Offset.center()
290261

291262
attributes.isDrawLeader = point.extrude == true
292263

@@ -312,27 +283,14 @@ internal class KmlToRenderableConverter {
312283
}
313284
}
314285

315-
private fun LatLonBox.toSector(): Sector? {
316-
val north = north ?: return null
317-
val south = south ?: return null
318-
val east = east ?: return null
319-
val west = west ?: return null
320-
321-
return Sector(
322-
Angle.fromDegrees(south),
323-
Angle.fromDegrees(north),
324-
Angle.fromDegrees(west),
325-
Angle.fromDegrees(east)
326-
)
327-
}
286+
private fun LatLonBox.toSector() = Sector(south.degrees, north.degrees, west.degrees, east.degrees)
328287

329288
private fun TextAttributes.applyStyle(labelStyle: LabelStyle?) {
330289
apply {
331290
labelStyle?.color?.let {
332291
textColor = fromHexABRG(it)
333292
outlineColor = textColor.toContrastColor()
334293
}
335-
labelStyle?.width?.let { outlineWidth = it }
336294
textOffset = Offset.center()
337295
}
338296
}
@@ -344,8 +302,8 @@ internal class KmlToRenderableConverter {
344302

345303
private fun AbstractShape.applyStyleOnShapeAttributes(style: Style?) {
346304
attributes.apply {
347-
val lineStyle = style?.stylesList?.filterIsInstance<LineStyle>()?.firstOrNull()
348-
val polyStyle = style?.stylesList?.filterIsInstance<PolyStyle>()?.firstOrNull()
305+
val lineStyle = style?.styles?.filterIsInstance<LineStyle>()?.firstOrNull()
306+
val polyStyle = style?.styles?.filterIsInstance<PolyStyle>()?.firstOrNull()
349307

350308
outlineColor = lineStyle?.color?.let { fromHexABRG(it) } ?: defaultLineColor
351309
interiorColor = polyStyle?.color?.let { fromHexABRG(it) } ?: defaultFillColor
@@ -360,10 +318,16 @@ internal class KmlToRenderableConverter {
360318
}
361319
}
362320

363-
private fun getAltitudeModeFrom(value: String?) = when (value) {
364-
"absolute" -> AltitudeMode.ABOVE_SEA_LEVEL
365-
"clampToGround" -> AltitudeMode.CLAMP_TO_GROUND
366-
"relativeToGround" -> AltitudeMode.RELATIVE_TO_GROUND
321+
private fun getAltitudeModeFrom(value: KMLAltitudeMode?) = when (value) {
322+
KMLAltitudeMode.absolute -> AltitudeMode.ABOVE_SEA_LEVEL
323+
KMLAltitudeMode.clampToGround -> AltitudeMode.CLAMP_TO_GROUND
324+
KMLAltitudeMode.relativeToGround -> AltitudeMode.RELATIVE_TO_GROUND
367325
else -> AltitudeMode.CLAMP_TO_GROUND
368326
}
327+
328+
private fun getOffsetModeFrom(value: HotSpotUnits) = when (value) {
329+
HotSpotUnits.pixels -> OffsetMode.PIXELS
330+
HotSpotUnits.fraction -> OffsetMode.FRACTION
331+
HotSpotUnits.insetPixels -> OffsetMode.INSET_PIXELS
332+
}
369333
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package earth.worldwind.formats.kml.models
2+
3+
import kotlinx.serialization.Serializable
4+
import nl.adaptivity.xmlutil.serialization.XmlSerialName
5+
6+
/**
7+
* This is an abstract element and cannot be used directly in a KML file.
8+
* This element is extended by the [Camera] and [LookAt] elements.
9+
*/
10+
@Serializable
11+
internal abstract class AbstractView(
12+
/**
13+
* Defines the horizontal field of view of the AbstractView during a tour.
14+
* This element has no effect on AbstractViews outside of a tour. [horizFov] is inserted automatically by
15+
* the Google Earth client (versions 6.1+) during tour recording.
16+
* Regular AbstractViews are assigned a value of 60; views within Street View are assigned a value of 85 to match
17+
* the standard Street View field of view in Google Earth. Once set, the value will be applied to subsequent views,
18+
* until a new value is specified.
19+
*/
20+
@XmlSerialName(prefix = "gx", value = "horizFov")
21+
val horizFov: Double = 60.0
22+
) : AbstractKml()

0 commit comments

Comments
 (0)