Merge branch 'release/1.8'
This commit is contained in:
commit
729113b428
@ -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
|
||||||
|
2
pom.xml
2
pom.xml
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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 = "") {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
243
src/test/kotlin/me/bvn13/sdk/android/gpx/ReadWriteTest.kt
Normal file
243
src/test/kotlin/me/bvn13/sdk/android/gpx/ReadWriteTest.kt
Normal 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
6459
src/test/resources/test.gpx
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user