Merge branch 'release/1.8'

This commit is contained in:
bvn13 2023-02-13 20:05:51 +03:00
commit 729113b428
9 changed files with 6841 additions and 46 deletions

View File

@ -1,5 +1,7 @@
# Android (Kotlin) SDK for manipulating GPX files # Android (Kotlin) SDK for manipulating GPX files
![](https://img.shields.io/maven-central/v/me.bvn13.sdk.android.gpx/GpxAndroidSdk)
## About ## About
This is another one SDK for reading and writing (manipulating) GPX files. This is another one SDK for reading and writing (manipulating) GPX files.
@ -8,6 +10,12 @@ Official GPX format is on [topografix](https://www.topografix.com/GPX/1/1/) site
## Changelog ## Changelog
### 2023-02-13
1) Fixed missed extensions
2) ✅ Tested on reading with self written content
3) ✅ Tested on reading [OsmAnd](https://osmand.net) GPX files
### 2022-12-18 ### 2022-12-18
1) implemented GPX format reader 1) implemented GPX format reader

View File

@ -6,7 +6,7 @@
<artifactId>GpxAndroidSdk</artifactId> <artifactId>GpxAndroidSdk</artifactId>
<groupId>me.bvn13.sdk.android.gpx</groupId> <groupId>me.bvn13.sdk.android.gpx</groupId>
<version>1.7</version> <version>1.8</version>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -92,10 +92,13 @@ package me.bvn13.sdk.android.gpx
* *
* [parameters] Map of key-value pairs * [parameters] Map of key-value pairs
*/ */
class ExtensionType(val nodeName: String, val value: String? = null, val parameters: Map<String, String>? = null) { class ExtensionType(val nodeName: String,
val value: String? = null,
val parameters: Map<String, String>? = null,
val nested: List<ExtensionType>? = null) {
init { init {
require(value != null || parameters != null) { require(value != null || parameters != null || nested != null) {
"value or parameters must be specified" "value or parameters or nesting elements must be specified for ${nodeName}"
} }
} }
@ -108,6 +111,7 @@ class ExtensionType(val nodeName: String, val value: String? = null, val paramet
if (nodeName != other.nodeName) return false if (nodeName != other.nodeName) return false
if (value != other.value) return false if (value != other.value) return false
if (parameters != other.parameters) return false if (parameters != other.parameters) return false
if (nested != other.nested) return false
return true return true
} }
@ -116,8 +120,7 @@ class ExtensionType(val nodeName: String, val value: String? = null, val paramet
var result = nodeName.hashCode() var result = nodeName.hashCode()
result = 31 * result + (value?.hashCode() ?: 0) result = 31 * result + (value?.hashCode() ?: 0)
result = 31 * result + (parameters?.hashCode() ?: 0) result = 31 * result + (parameters?.hashCode() ?: 0)
result = 31 * result + (nested?.hashCode() ?: 0)
return result return result
} }
} }

View File

@ -6,6 +6,7 @@ import java.time.format.DateTimeFormatterBuilder
class GpxConstant { class GpxConstant {
companion object { companion object {
const val HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" const val HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
const val HEADER_EXTENDED = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
const val VERSION = "1.1" const val VERSION = "1.1"
val DTF = val DTF =
DateTimeFormatterBuilder().append(ISO_LOCAL_DATE_TIME) // use the existing formatter for date time DateTimeFormatterBuilder().append(ISO_LOCAL_DATE_TIME) // use the existing formatter for date time

View File

@ -3,6 +3,7 @@ package me.bvn13.sdk.android.gpx
import me.bvn13.sdk.android.gpx.GpxConstant.Companion.DTF import me.bvn13.sdk.android.gpx.GpxConstant.Companion.DTF
import java.io.InputStream import java.io.InputStream
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.stream.Collectors
fun GpxType.Companion.read(dis: InputStream) = GpxReader().read(dis) fun GpxType.Companion.read(dis: InputStream) = GpxReader().read(dis)
@ -28,7 +29,13 @@ class GpxReader {
private fun readSignature(dis: InputStream, buffer: Container): GpxType { private fun readSignature(dis: InputStream, buffer: Container): GpxType {
val container = readUntil(dis, buffer, '\n') val container = readUntil(dis, buffer, '\n')
if ("${GpxConstant.HEADER}\n" != container.buffer.asString()) { val signaturePrepared = container.buffer.asString()
.trim()
.replace("'", "\"")
.replace(" ?", "?")
if (GpxConstant.HEADER != signaturePrepared
&& GpxConstant.HEADER_EXTENDED != signaturePrepared
) {
throw IllegalArgumentException("Wrong xml signature!") throw IllegalArgumentException("Wrong xml signature!")
} }
return readBeginning(dis, Container.empty(container.position)) return readBeginning(dis, Container.empty(container.position))
@ -59,10 +66,12 @@ class GpxReader {
return findObject(container.objects, "gpx").let { return findObject(container.objects, "gpx").let {
return GpxType( return GpxType(
metadata = assembleMetadataType(it.nested), metadata = assembleMetadataType(it.nested),
creator = findAttributeOrNull(it.attributes, "creator") ?: throw IllegalArgumentException("Gpx.Creator not found"), creator = findAttributeOrNull(it.attributes, "creator")
wpt = findObjectsOrNull(it.nested ,"wpt")?.map { assembleWptType(it) }, ?: throw IllegalArgumentException("Gpx.Creator not found"),
wpt = findObjectsOrNull(it.nested, "wpt")?.map { assembleWptType(it) },
rte = findObjectsOrNull(it.nested, "rte")?.map { assembleRteType(it) }, rte = findObjectsOrNull(it.nested, "rte")?.map { assembleRteType(it) },
trk = findObjectsOrNull(it.nested, "trk")?.map { assembleTrkType(it) } trk = findObjectsOrNull(it.nested, "trk")?.map { assembleTrkType(it) },
extensions = findObjectOrNull(it.nested, "extensions")?.let { assembleExtensionType(it) }
) )
} }
} }
@ -71,10 +80,13 @@ class GpxReader {
findObject(objects, "metadata") findObject(objects, "metadata")
.let { .let {
return MetadataType( return MetadataType(
name = findObjectOrNull(it.nested, "name")?.value ?: throw IllegalArgumentException("Gpx.Metadata.Name not found"), name = findObjectOrNull(it.nested, "name")?.value
description = findObjectOrNull(it.nested, "desc")?.value ?: throw IllegalArgumentException("Gpx.Metadata.Description not found"), ?: throw IllegalArgumentException("Gpx.Metadata.Name not found"),
authorName = findObject(it.nested, "author").let { author -> description = findObjectOrNull(it.nested, "desc")?.value
findObject(author.nested, "name").value ?: throw IllegalArgumentException("Gpx.Metadata.Author.Name not found") ?: "",
authorName = findObjectOrNull(it.nested, "author").let { author ->
findObjectOrNull(author?.nested, "name")?.value
?: ""
} }
) )
} }
@ -123,7 +135,10 @@ class GpxReader {
cmt = findObjectOrNull(obj.nested, "cmt")?.value, cmt = findObjectOrNull(obj.nested, "cmt")?.value,
desc = findObjectOrNull(obj.nested, "desc")?.value, desc = findObjectOrNull(obj.nested, "desc")?.value,
src = findObjectOrNull(obj.nested, "src")?.value, src = findObjectOrNull(obj.nested, "src")?.value,
link = findObjectsOrNull(obj.nested, "link")?.let { list -> if (list.isNotEmpty()) list.map { assembleLinkType(it) } else null }, link = findObjectsOrNull(
obj.nested,
"link"
)?.let { list -> if (list.isNotEmpty()) list.map { assembleLinkType(it) } else null },
number = findObjectOrNull(obj.nested, "number")?.value?.toInt(), number = findObjectOrNull(obj.nested, "number")?.value?.toInt(),
type = findObjectOrNull(obj.nested, "type")?.value, type = findObjectOrNull(obj.nested, "type")?.value,
extensions = findObjectOrNull(obj.nested, "extensions")?.let { assembleExtensionType(it) }, extensions = findObjectOrNull(obj.nested, "extensions")?.let { assembleExtensionType(it) },
@ -140,12 +155,17 @@ class GpxReader {
private fun assembleFixType(value: String): FixType = private fun assembleFixType(value: String): FixType =
FixType.valueOf(value.uppercase()) FixType.valueOf(value.uppercase())
private fun assembleExtensionType(obj: XmlObject): ExtensionType = private fun assembleExtensionType(obj: XmlObject): ExtensionType {
ExtensionType( val nested: List<ExtensionType>? = obj.nested?.stream()
?.map { assembleExtensionType(it) }
?.collect(Collectors.toList())
return ExtensionType(
nodeName = obj.type, nodeName = obj.type,
value = if (obj.value == "") null else obj.value, value = if (obj.value == "") null else obj.value,
parameters = if (obj.attributes.isEmpty()) null else obj.attributes.toSortedMap() parameters = if (obj.attributes.isEmpty()) null else obj.attributes.toSortedMap(),
nested = nested
) )
}
private fun assembleTrksegType(obj: XmlObject): TrksegType = private fun assembleTrksegType(obj: XmlObject): TrksegType =
TrksegType( TrksegType(
@ -186,6 +206,7 @@ class GpxReader {
container = readAttributes(dis, container) container = readAttributes(dis, container)
xmlObject.attributes = container.attributes xmlObject.attributes = container.attributes
} }
if (!container.isShortClosing) {
container = readSkipping(dis, container, SKIPPING_SET) container = readSkipping(dis, container, SKIPPING_SET)
if (container.byte!!.toInt() == '<'.code) { if (container.byte!!.toInt() == '<'.code) {
container = readNestedObjects(dis, container) container = readNestedObjects(dis, container)
@ -195,6 +216,7 @@ class GpxReader {
if (container.buffer.asString() != "</$tagName>") { if (container.buffer.asString() != "</$tagName>") {
container = readValue(dis, container, tagName) container = readValue(dis, container, tagName)
} }
}
xmlObject.value = container.buffer.asString().replace("</$tagName>", "") xmlObject.value = container.buffer.asString().replace("</$tagName>", "")
container.objects = listOf(xmlObject) container.objects = listOf(xmlObject)
return container return container
@ -221,8 +243,12 @@ class GpxReader {
return container return container
} }
private fun readFinishingTag(dis: InputStream, buffer: Container, tagName: String): Container = private fun readFinishingTag(dis: InputStream, buffer: Container, tagName: String): Container {
readExactly(dis, buffer, "</${tagName}>") if (buffer.isShortClosing) {
return buffer
}
return readExactly(dis, buffer, "</${tagName}>")
}
private fun readValue(dis: InputStream, buffer: Container, tagName: String): Container = private fun readValue(dis: InputStream, buffer: Container, tagName: String): Container =
readUntil(dis, buffer, "</$tagName>") readUntil(dis, buffer, "</$tagName>")
@ -236,8 +262,10 @@ class GpxReader {
do { do {
attributeContainer = readAttribute(dis, attributeContainer) attributeContainer = readAttribute(dis, attributeContainer)
attributes.putAll(attributeContainer.attributes) attributes.putAll(attributeContainer.attributes)
} while (attributeContainer.attributes.isNotEmpty() && attributeContainer.byte!!.toInt() != '>'.code) } while (attributeContainer.attributes.isNotEmpty()
val result = Container.emptyWithAttributes(attributeContainer.position, attributes) && attributeContainer.byte!!.toInt() != '>'.code
)
val result = Container.emptyWithAttributes(attributeContainer.position, attributes, attributeContainer.isShortClosing)
return result return result
} }
@ -250,8 +278,17 @@ class GpxReader {
val valueAsString = valueContainer.buffer.asString() val valueAsString = valueContainer.buffer.asString()
val value = valueAsString.substring(0, valueAsString.length - 1) val value = valueAsString.substring(0, valueAsString.length - 1)
val result = Container.empty(valueContainer.position) val result = Container.empty(valueContainer.position)
val closingContainer = readSkipping(dis, result, SKIPPING_SET) var closingContainer = readSkipping(dis, result, SKIPPING_SET)
val nextBlockContainer = Container.of(closingContainer.byte!!, closingContainer.position) var isShortClosing = false
if (closingContainer.byte!!.toInt() == '/'.code) {
val endingContainer = readByte(dis, closingContainer)
if (endingContainer.byte!!.toInt() != '>'.code) {
throw IllegalArgumentException("There must be valid closing tag at ${endingContainer.position}")
}
closingContainer = endingContainer
isShortClosing = true
}
val nextBlockContainer = Container.of(closingContainer.byte!!, closingContainer.position, isShortClosing)
nextBlockContainer.attributes = mapOf(name to value) nextBlockContainer.attributes = mapOf(name to value)
return nextBlockContainer return nextBlockContainer
} }
@ -321,28 +358,42 @@ class GpxReader {
private fun readByte(dis: InputStream, container: Container): Container { private fun readByte(dis: InputStream, container: Container): Container {
val ba = ByteArray(1); val ba = ByteArray(1);
if (-1 == dis.read(ba, 0, 1)) { if (-1 == dis.read(ba, 0, 1)) {
throw InterruptedException("EOF") throw InterruptedException("EOF at ${container.position}\nUnparsed data: " + String(container.buffer))
} else if (ba.size != 1) { } else if (ba.size != 1) {
throw InterruptedException("Reading of 1 byte returns ${ba.size} bytes") throw InterruptedException("Reading of 1 byte returns ${ba.size} bytes at ${container.position}")
} }
return Container(container.position + 1, ba[0], container.buffer.plus(ba)) return Container(container.position + 1, ba[0], container.buffer.plus(ba))
} }
class Container(val position: Long = 0, val byte: Byte?, val buffer: ByteArray) { class Container(
val position: Long = 0,
val byte: Byte?,
val buffer: ByteArray,
val isShortClosing: Boolean = false
) {
var objects: List<XmlObject>? = null var objects: List<XmlObject>? = null
var attributes: Map<String, String> = HashMap() var attributes: Map<String, String> = HashMap()
var value: String? = null var value: String? = null
companion object { companion object {
fun empty(): Container = empty(0) fun empty(): Container = empty(0)
fun empty(position: Long) = Container(position, null, ByteArray(0)) fun empty(position: Long, isShortClosing: Boolean = false) = Container(position, null, ByteArray(0), isShortClosing)
fun emptyWithAttributes(position: Long, attributes: Map<String, String>): Container { fun emptyWithAttributes(position: Long, attributes: Map<String, String>, isShortClosing: Boolean = false): Container {
val container = Container.empty(position) val container = Container.empty(position, isShortClosing)
container.attributes = attributes container.attributes = attributes
return container return container
} }
fun of(b: Byte, position: Long) = Container(position, b, ByteArray(1) { _ -> b }) fun of(b: Byte, position: Long, isShortClosing: Boolean = false) =
Container(position, b, ByteArray(1) { _ -> b }, isShortClosing)
fun isShortClosing(c: Container, buffer: ByteArray): Container {
val container = Container(c.position, c.byte, buffer, isShortClosing = true)
container.objects = c.objects
container.attributes = c.attributes
container.value = c.value
return container
}
} }
override fun toString(): String = this.buffer.asString() override fun toString(): String = this.buffer.asString()

View File

@ -100,6 +100,7 @@ fun GpxType.toXmlString(clock: Clock?): String = """
${this.wpt?.toXmlString() ?: ""} ${this.wpt?.toXmlString() ?: ""}
${this.rte?.toXmlString() ?: ""} ${this.rte?.toXmlString() ?: ""}
${this.trk?.toXmlString() ?: ""} ${this.trk?.toXmlString() ?: ""}
${this.extensions?.toXmlString() ?: ""}
</gpx> </gpx>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
@ -140,7 +141,7 @@ fun WptType.toXmlString(nodeName: String) = """
${toXmlString(pdop, "pdop")} ${toXmlString(pdop, "pdop")}
${toXmlString(ageofgpsdata, "ageofgpsdata")} ${toXmlString(ageofgpsdata, "ageofgpsdata")}
${toXmlString(dgpsid, "dgpsid")} ${toXmlString(dgpsid, "dgpsid")}
${extensions?.toXmlString() ?: ""} ${extensions?.toXmlString(true) ?: ""}
</${nodeName}> </${nodeName}>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
@ -153,7 +154,7 @@ fun RteType.toXmlString() = """
${this.link?.toXmlString() ?: ""} ${this.link?.toXmlString() ?: ""}
${toXmlString(this.number, "number")} ${toXmlString(this.number, "number")}
${toXmlString(this.type, "type")} ${toXmlString(this.type, "type")}
${this.extensions?.toXmlString() ?: ""} ${this.extensions?.toXmlString(true) ?: ""}
${this.rtept?.toXmlString("rtept") ?: ""} ${this.rtept?.toXmlString("rtept") ?: ""}
</rte> </rte>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
@ -183,13 +184,21 @@ fun FixType.toXmlString() = """
<fix>${this.value}</fix> <fix>${this.value}</fix>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
fun ExtensionType.toXmlString() = """ fun ExtensionType.toXmlString() =
if ((this.nested?.size ?: 0) > 0) {
"""
<${this.nodeName}${toXmlString(this.parameters)}>${this.nested?.toXmlString() ?: ""}</${this.nodeName}>
""".trim().removeEmptyStrings()
} else {
"""
<${this.nodeName}${toXmlString(this.parameters)}>${this.value ?: ""}</${this.nodeName}> <${this.nodeName}${toXmlString(this.parameters)}>${this.value ?: ""}</${this.nodeName}>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
}
fun TrksegType.toXmlString() = """ fun TrksegType.toXmlString() = """
<trkseg> <trkseg>
${this.trkpt?.toXmlString("trkpt") ?: ""} ${this.trkpt?.toXmlString("trkpt") ?: ""}
${this.extensions?.toXmlString() ?: ""}
</trkseg> </trkseg>
""".trim().removeEmptyStrings() """.trim().removeEmptyStrings()
@ -197,9 +206,17 @@ fun List<WptType>.toXmlString(nodeName: String?) = this.joinToString(prefix = "\
it.toXmlString(nodeName) it.toXmlString(nodeName)
} }
fun List<ExtensionType>.toXmlString() = this.joinToString( fun List<ExtensionType>.toXmlString(inGroup: Boolean = false): String {
if (inGroup) {
return this.joinToString(
prefix = "<extensions>\n", postfix = "\n</extensions>", separator = "\n", transform = ExtensionType::toXmlString prefix = "<extensions>\n", postfix = "\n</extensions>", separator = "\n", transform = ExtensionType::toXmlString
) )
} else {
return this.joinToString(
prefix = "\n", postfix = "\n", separator = "\n", transform = ExtensionType::toXmlString
)
}
}
@JvmName("toXmlStringWptType") @JvmName("toXmlStringWptType")
fun List<WptType>.toXmlString() = this.joinToString(prefix = "", postfix = "", separator = "") { fun List<WptType>.toXmlString() = this.joinToString(prefix = "", postfix = "", separator = "") {

View File

@ -1,5 +1,6 @@
package me.bvn13.sdk.android.gpx package me.bvn13.sdk.android.gpx
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.time.* import java.time.*
@ -409,4 +410,16 @@ class GpxReaderTest {
val gpx = GpxType.read(gpxString.byteInputStream()) val gpx = GpxType.read(gpxString.byteInputStream())
assertEquals(gpxType, gpx) assertEquals(gpxType, gpx)
} }
@DisplayName("Read test.gpx (generated in OsmAnd Android application")
@Test
fun readTestGpx() {
val gpxType = GpxType.read(javaClass.classLoader.getResource("test.gpx").openStream())
Assertions.assertEquals(1011, gpxType.trk?.get(0)?.trkseg?.get(0)?.trkpt?.size ?: 0)
Assertions.assertEquals(1, gpxType.trk?.get(0)?.trkseg?.get(0)?.trkpt?.get(0)?.extensions?.size ?: 0)
Assertions.assertEquals(2, gpxType.trk?.get(0)?.trkseg?.get(0)?.extensions?.nested?.size ?: 0)
Assertions.assertEquals(223, gpxType.trk?.get(0)?.trkseg?.get(0)?.extensions?.nested?.get(0)?.nested?.size ?: 0)
Assertions.assertEquals(159, gpxType.trk?.get(0)?.trkseg?.get(0)?.extensions?.nested?.get(1)?.nested?.size ?: 0)
Assertions.assertEquals(1, gpxType.extensions?.nested?.size ?: 0)
}
} }

View File

@ -0,0 +1,243 @@
package me.bvn13.sdk.android.gpx
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream
import java.time.*
class ReadWriteTest {
@DisplayName("Read-Write test")
@Test
fun testReadWrite() {
val clock = Clock.fixed(
LocalDateTime.of(2022, 9, 24, 15, 4, 0, 0).toInstant(ZoneOffset.ofHours(3)), ZoneId.of("Europe/Moscow")
)
val gpxType = GpxType(
MetadataType("test name", description = "test description", authorName = "bvn13"),
wpt = listOf(
WptType(
lat = 14.64736838389662,
lon = 7.93212890625,
ele = 10.toDouble(),
time = OffsetDateTime.now(clock),
magvar = 3.toDouble(),
geoidheight = 45.toDouble(),
name = "test point 1",
cmt = "comment 1",
desc = "description of point 1",
link = listOf(
LinkType(
href = "http://link-to.site.href",
text = "text",
type = "hyperlink"
),
LinkType(
href = "http://link2-to.site.href",
text = "text2",
type = "hyperlink2"
)
),
src = "source 1",
sym = "sym 1",
type = "type 1",
fix = FixType.DGPS,
sat = 1,
hdop = 55.toDouble(),
vdop = 66.toDouble(),
pdop = 77.toDouble(),
ageofgpsdata = 44,
dgpsid = 88,
extensions = listOf(
ExtensionType(
"extension1",
parameters = mapOf(Pair("first", "second"), Pair("third", "fours"))
),
ExtensionType(
"extension2",
parameters = mapOf(Pair("aa", "bb"), Pair("cc", "dd"))
)
)
)
),
rte = listOf(
RteType(
name = "rte name",
cmt = "cmt",
desc = "desc",
src = "src",
link = listOf(
LinkType(
href = "https://new.link.rte",
text = "new text rte",
type = "hyperlink"
)
),
number = 1234,
type = "route",
extensions = listOf(
ExtensionType(
"ext-1",
value = "value1"
)
),
rtept = listOf(
WptType(
lat = 14.64736838389662,
lon = 7.93212890625,
ele = 10.toDouble(),
time = OffsetDateTime.now(clock),
magvar = 3.toDouble(),
geoidheight = 45.toDouble(),
name = "test point 1",
cmt = "comment 1",
desc = "description of point 1",
link = listOf(
LinkType(
href = "http://link-to.site.href",
text = "text",
type = "hyperlink"
),
LinkType(
href = "http://link2-to.site.href",
text = "text2",
type = "hyperlink2"
)
),
src = "source 1",
sym = "sym 1",
type = "type 1",
fix = FixType.DGPS,
sat = 1,
hdop = 55.toDouble(),
vdop = 66.toDouble(),
pdop = 77.toDouble(),
ageofgpsdata = 44,
dgpsid = 88,
extensions = listOf(
ExtensionType(
"extension1",
parameters = mapOf(Pair("first", "second"), Pair("third", "fours"))
),
ExtensionType(
"extension2",
parameters = mapOf(Pair("aa", "bb"), Pair("cc", "dd"))
)
)
)
)
)
),
trk = listOf(
TrkType(
name = "track 1",
cmt = "comment track 1",
desc = "desc track 1",
src = "src track 1",
number = 1234,
type = "type 1",
trkseg = listOf(
TrksegType(
listOf(
WptType(
lat = 14.64736838389662,
lon = 7.93212890625,
ele = 10.toDouble(),
time = OffsetDateTime.now(clock),
magvar = 3.toDouble(),
geoidheight = 45.toDouble(),
name = "test point 1",
cmt = "comment 1",
desc = "description of point 1",
link = listOf(
LinkType(
href = "http://link-to.site.href",
text = "text",
type = "hyperlink"
),
LinkType(
href = "http://link2-to.site.href",
text = "text2",
type = "hyperlink2"
)
),
src = "source 1",
sym = "sym 1",
type = "type 1",
fix = FixType.DGPS,
sat = 1,
hdop = 55.toDouble(),
vdop = 66.toDouble(),
pdop = 77.toDouble(),
ageofgpsdata = 44,
dgpsid = 88,
extensions = listOf(
ExtensionType(
"extension1",
parameters = mapOf(Pair("first", "second"), Pair("third", "fours"))
),
ExtensionType(
"extension2",
parameters = mapOf(Pair("aa", "bb"), Pair("cc", "dd"))
)
)
),
WptType(
lat = 14.64736838389662,
lon = 7.93212890625,
ele = 10.toDouble(),
time = OffsetDateTime.now(clock),
magvar = 3.toDouble(),
geoidheight = 45.toDouble(),
name = "test point 1",
cmt = "comment 1",
desc = "description of point 1",
link = listOf(
LinkType(
href = "http://link-to.site.href",
text = "text",
type = "hyperlink"
),
LinkType(
href = "http://link2-to.site.href",
text = "text2",
type = "hyperlink2"
)
),
src = "source 1",
sym = "sym 1",
type = "type 1",
fix = FixType.DGPS,
sat = 1,
hdop = 55.toDouble(),
vdop = 66.toDouble(),
pdop = 77.toDouble(),
ageofgpsdata = 44,
dgpsid = 88,
extensions = listOf(
ExtensionType(
"extension1",
parameters = mapOf(Pair("first", "second"), Pair("third", "fours"))
),
ExtensionType(
"extension2",
parameters = mapOf(Pair("aa", "bb"), Pair("cc", "dd"))
)
)
)
)
)
)
)
)
)
val gpx = gpxType.toXmlString(clock)
val deserializedGpxType = GpxType.read(ByteArrayInputStream(gpx.toByteArray()))
Assertions.assertEquals(gpxType, deserializedGpxType)
}
}

6459
src/test/resources/test.gpx Normal file

File diff suppressed because it is too large Load Diff