Merge pull request 'feature/1/reader' (#2) from feature/1/reader into master
Reviewed-on: bvn13/GpxAndroidSdk#2
This commit is contained in:
commit
064a1dc7b6
@ -40,42 +40,8 @@
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.8.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.7.10" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-runner-junit5-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-framework-api-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.6.4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-common-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-framework-engine-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.github.classgraph:classgraph:4.8.149" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.github.ajalt:mordant:1.2.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.github.ajalt:colormath:1.2.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.6.4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.java.dev.jna:jna:5.9.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.java.dev.jna:jna-platform:5.9.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.10.9" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.10.9" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-framework-discovery-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-extensions-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: commons-io:commons-io:2.11.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-common:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-dsl:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-dsl-jvm:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-agent-jvm:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-agent-api:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.mockk:mockk-agent-common:1.12.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:3.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-framework-concurrency-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-suite-api:1.7.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-launcher:1.7.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-reflect:1.6.21" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-assertions-core-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-assertions-shared-jvm:5.4.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.github.java-diff-utils:java-diff-utils:4.12" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: io.kotest:kotest-assertions-api-jvm:5.4.2" level="project" />
|
||||
</component>
|
||||
</module>
|
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<artifactId>GpxAndroidSdk</artifactId>
|
||||
<groupId>me.bvn13.sdk.android.gpx</groupId>
|
||||
<version>1.3</version>
|
||||
<version>1.4-SNAPSHOT</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
@ -98,4 +98,26 @@ class ExtensionType(val nodeName: String, val value: String? = null, val paramet
|
||||
"value or parameters must be specified"
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ExtensionType
|
||||
|
||||
if (nodeName != other.nodeName) return false
|
||||
if (value != other.value) return false
|
||||
if (parameters != other.parameters) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = nodeName.hashCode()
|
||||
result = 31 * result + (value?.hashCode() ?: 0)
|
||||
result = 31 * result + (parameters?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
15
src/main/kotlin/me/bvn13/sdk/android/gpx/GpxConstant.kt
Normal file
15
src/main/kotlin/me/bvn13/sdk/android/gpx/GpxConstant.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package me.bvn13.sdk.android.gpx
|
||||
|
||||
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
|
||||
class GpxConstant {
|
||||
companion object {
|
||||
const val HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
const val VERSION = "1.1"
|
||||
val DTF =
|
||||
DateTimeFormatterBuilder().append(ISO_LOCAL_DATE_TIME) // use the existing formatter for date time
|
||||
.appendOffset("+HH:MM", "+00:00") // set 'noOffsetText' to desired '+00:00'
|
||||
.toFormatter()
|
||||
}
|
||||
}
|
365
src/main/kotlin/me/bvn13/sdk/android/gpx/GpxReader.kt
Normal file
365
src/main/kotlin/me/bvn13/sdk/android/gpx/GpxReader.kt
Normal file
@ -0,0 +1,365 @@
|
||||
package me.bvn13.sdk.android.gpx
|
||||
|
||||
import me.bvn13.sdk.android.gpx.GpxConstant.Companion.DTF
|
||||
import java.io.InputStream
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
fun GpxType.Companion.read(dis: InputStream) = GpxReader().read(dis)
|
||||
|
||||
fun ByteArray.asString() = String(this)
|
||||
|
||||
class GpxReader {
|
||||
fun read(dis: InputStream): GpxType {
|
||||
val container = readNotSpace(dis, Container.empty())
|
||||
return when (container.byte!!.toInt()) {
|
||||
'<'.code -> readBeginning(dis, container)
|
||||
else -> throw IllegalArgumentException("Not a GPX/XML?")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readBeginning(dis: InputStream, buffer: Container): GpxType {
|
||||
val container = readByte(dis, buffer)
|
||||
return when (container.byte!!.toInt()) {
|
||||
'?'.code -> readSignature(dis, container)
|
||||
'<'.code -> readGpx(dis, container)
|
||||
else -> throw IllegalArgumentException("Wrong symbol at ${container.position} from the very beginning")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readSignature(dis: InputStream, buffer: Container): GpxType {
|
||||
val container = readUntil(dis, buffer, '\n')
|
||||
if ("${GpxConstant.HEADER}\n" != container.buffer.asString()) {
|
||||
throw IllegalArgumentException("Wrong xml signature!")
|
||||
}
|
||||
return readBeginning(dis, Container.empty(container.position))
|
||||
}
|
||||
|
||||
private fun readGpx(dis: InputStream, buffer: Container): GpxType {
|
||||
var tagName: String? = null
|
||||
val container = readObject(dis, buffer) {
|
||||
tagName = it.buffer.asString().substring(1, it.buffer.size - 1).lowercase()
|
||||
if ("gpx" != tagName) {
|
||||
throw IllegalArgumentException("There must be GPX tag in given InputStream")
|
||||
}
|
||||
}
|
||||
tagName?.let {
|
||||
return assembleGpxType(container)
|
||||
} ?: run {
|
||||
throw IllegalArgumentException("Unable to read content")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assembleGpxType(container: Container): GpxType {
|
||||
if (container.objects == null) {
|
||||
throw IllegalArgumentException("It was not parsed properly")
|
||||
}
|
||||
if (container.objects!!.size != 1) {
|
||||
throw IllegalArgumentException("It was parsed ${container.objects!!.size} objects, but only 1 is expected")
|
||||
}
|
||||
return findObject(container.objects, "gpx").let {
|
||||
return GpxType(
|
||||
metadata = assembleMetadataType(it.nested),
|
||||
creator = findAttributeOrNull(it.attributes, "creator") ?: throw IllegalArgumentException("Gpx.Creator not found"),
|
||||
wpt = findObjectsOrNull(it.nested ,"wpt")?.map { assembleWptType(it) },
|
||||
rte = findObjectsOrNull(it.nested, "rte")?.map { assembleRteType(it) },
|
||||
trk = findObjectsOrNull(it.nested, "trk")?.map { assembleTrkType(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assembleMetadataType(objects: List<XmlObject>?): MetadataType =
|
||||
findObject(objects, "metadata")
|
||||
.let {
|
||||
return MetadataType(
|
||||
name = findObjectOrNull(it.nested, "name")?.value ?: throw IllegalArgumentException("Gpx.Metadata.Name not found"),
|
||||
description = findObjectOrNull(it.nested, "desc")?.value ?: throw IllegalArgumentException("Gpx.Metadata.Description not found"),
|
||||
authorName = findObject(it.nested, "author").let { author ->
|
||||
findObject(author.nested, "name").value ?: throw IllegalArgumentException("Gpx.Metadata.Author.Name not found")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun assembleWptType(obj: XmlObject): WptType =
|
||||
WptType(
|
||||
lat = findAttribute(obj.attributes, "lat").toDouble(),
|
||||
lon = findAttribute(obj.attributes, "lon").toDouble(),
|
||||
ele = findObjectOrNull(obj.nested, "ele")?.value?.toDouble(),
|
||||
time = findObjectOrNull(obj.nested, "time")?.value?.let { OffsetDateTime.parse(it, DTF) },
|
||||
magvar = findObjectOrNull(obj.nested, "magvar")?.value?.toDouble(),
|
||||
geoidheight = findObjectOrNull(obj.nested, "geoidheight")?.value?.toDouble(),
|
||||
name = findObjectOrNull(obj.nested, "name")?.value,
|
||||
cmt = findObjectOrNull(obj.nested, "cmt")?.value,
|
||||
desc = findObjectOrNull(obj.nested, "desc")?.value,
|
||||
src = findObjectOrNull(obj.nested, "src")?.value,
|
||||
link = findObjectsOrNull(obj.nested, "link")?.map { assembleLinkType(it) },
|
||||
sym = findObjectOrNull(obj.nested, "sym")?.value,
|
||||
type = findObjectOrNull(obj.nested, "type")?.value,
|
||||
fix = findObjectOrNull(obj.nested, "fix")?.value?.let { assembleFixType(it) },
|
||||
sat = findObjectOrNull(obj.nested, "sat")?.value?.toInt(),
|
||||
hdop = findObjectOrNull(obj.nested, "hdop")?.value?.toDouble(),
|
||||
vdop = findObjectOrNull(obj.nested, "vdop")?.value?.toDouble(),
|
||||
pdop = findObjectOrNull(obj.nested, "pdop")?.value?.toDouble(),
|
||||
ageofgpsdata = findObjectOrNull(obj.nested, "ageofgpsdata")?.value?.toInt(),
|
||||
dgpsid = findObjectOrNull(obj.nested, "dgpsid")?.value?.toInt(),
|
||||
extensions = findObjectOrNull(obj.nested, "extensions")?.nested?.map { assembleExtensionType(it) }
|
||||
)
|
||||
|
||||
private fun assembleRteType(obj: XmlObject): RteType =
|
||||
RteType(
|
||||
name = findObjectOrNull(obj.nested, "name")?.value,
|
||||
cmt = findObjectOrNull(obj.nested, "cmt")?.value,
|
||||
desc = findObjectOrNull(obj.nested, "desc")?.value,
|
||||
src = findObjectOrNull(obj.nested, "src")?.value,
|
||||
link = findObjectsOrNull(obj.nested, "link")?.map { assembleLinkType(it) },
|
||||
number = findObjectOrNull(obj.nested, "number")?.value?.toInt(),
|
||||
type = findObjectOrNull(obj.nested, "type")?.value,
|
||||
extensions = findObjectOrNull(obj.nested, "extensions")?.nested?.map { assembleExtensionType(it) },
|
||||
rtept = findObjectsOrNull(obj.nested, "rtept")?.map { assembleWptType(it) }
|
||||
)
|
||||
|
||||
private fun assembleTrkType(obj: XmlObject): TrkType =
|
||||
TrkType(
|
||||
name = findObjectOrNull(obj.nested, "name")?.value,
|
||||
cmt = findObjectOrNull(obj.nested, "cmt")?.value,
|
||||
desc = findObjectOrNull(obj.nested, "desc")?.value,
|
||||
src = findObjectOrNull(obj.nested, "src")?.value,
|
||||
link = findObjectsOrNull(obj.nested, "link")?.let { list -> if (list.isNotEmpty()) list.map { assembleLinkType(it) } else null },
|
||||
number = findObjectOrNull(obj.nested, "number")?.value?.toInt(),
|
||||
type = findObjectOrNull(obj.nested, "type")?.value,
|
||||
extensions = findObjectOrNull(obj.nested, "extensions")?.let { assembleExtensionType(it) },
|
||||
trkseg = findObjectsOrNull(obj.nested, "trkseg")?.map { assembleTrksegType(it) }
|
||||
)
|
||||
|
||||
private fun assembleLinkType(obj: XmlObject): LinkType =
|
||||
LinkType(
|
||||
href = findAttribute(obj.attributes, "href"),
|
||||
text = findObjectOrNull(obj.nested, "text")?.value,
|
||||
type = findObjectOrNull(obj.nested, "type")?.value
|
||||
)
|
||||
|
||||
private fun assembleFixType(value: String): FixType =
|
||||
FixType.valueOf(value.uppercase())
|
||||
|
||||
private fun assembleExtensionType(obj: XmlObject): ExtensionType =
|
||||
ExtensionType(
|
||||
nodeName = obj.type,
|
||||
value = if (obj.value == "") null else obj.value,
|
||||
parameters = if (obj.attributes.isEmpty()) null else obj.attributes.toSortedMap()
|
||||
)
|
||||
|
||||
private fun assembleTrksegType(obj: XmlObject): TrksegType =
|
||||
TrksegType(
|
||||
trkpt = findObjectsOrNull(obj.nested, "trkpt")?.map { assembleWptType(it) },
|
||||
extensions = findObjectOrNull(obj.nested, "extensions")?.let { assembleExtensionType(it) }
|
||||
)
|
||||
|
||||
private fun findObjectOrNull(objects: List<XmlObject>?, name: String): XmlObject? =
|
||||
objects?.firstOrNull { o ->
|
||||
o.type.lowercase() == name.lowercase()
|
||||
}
|
||||
|
||||
private fun findObject(objects: List<XmlObject>?, name: String): XmlObject =
|
||||
findObjectOrNull(objects, name)
|
||||
?: throw IllegalArgumentException("$name not found")
|
||||
|
||||
private fun findObjectsOrNull(objects: List<XmlObject>?, name: String): List<XmlObject>? =
|
||||
objects?.filter { o ->
|
||||
o.type.lowercase() == name.lowercase()
|
||||
}
|
||||
|
||||
private fun findAttributeOrNull(attributes: Map<String, String>?, name: String): String? =
|
||||
attributes?.firstNotNullOf { e -> if (e.key.lowercase() == name.lowercase()) e.value else null }
|
||||
|
||||
private fun findAttribute(attributes: Map<String, String>?, name: String): String =
|
||||
findAttributeOrNull(attributes, name)
|
||||
?: throw IllegalArgumentException("Unable to find attribute $name")
|
||||
|
||||
private fun readObject(dis: InputStream, buffer: Container, checker: ((Container) -> Unit)? = null): Container {
|
||||
if (buffer.buffer[0].toInt() != '<'.code) {
|
||||
throw IllegalArgumentException("Not a tag at position ${buffer.position}")
|
||||
}
|
||||
var container = readUntil(dis, buffer, setOf(' ', '\n', '>'))
|
||||
val tagName = container.buffer.asString().substring(1, container.buffer.size - 1).lowercase()
|
||||
checker?.invoke(container)
|
||||
val xmlObject = XmlObject(tagName)
|
||||
if (container.byte!!.toInt() != '>'.code) {
|
||||
container = readAttributes(dis, container)
|
||||
xmlObject.attributes = container.attributes
|
||||
}
|
||||
container = readSkipping(dis, container, SKIPPING_SET)
|
||||
if (container.byte!!.toInt() == '<'.code) {
|
||||
container = readNestedObjects(dis, container)
|
||||
xmlObject.nested = container.objects
|
||||
container = readFinishingTag(dis, container, tagName)
|
||||
}
|
||||
if (container.buffer.asString() != "</$tagName>") {
|
||||
container = readValue(dis, container, tagName)
|
||||
}
|
||||
xmlObject.value = container.buffer.asString().replace("</$tagName>", "")
|
||||
container.objects = listOf(xmlObject)
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readNestedObjects(dis: InputStream, buffer: Container): Container {
|
||||
val list = ArrayList<XmlObject>()
|
||||
var container: Container = readByte(dis, buffer)
|
||||
if (container.byte!!.toInt() == '/'.code) {
|
||||
return container
|
||||
}
|
||||
while (true) {
|
||||
val objectContainer = readObject(dis, container)
|
||||
val skippingContainer = readSkipping(dis, objectContainer, SKIPPING_SET)
|
||||
container = readByte(dis, skippingContainer)
|
||||
objectContainer.objects?.forEach {
|
||||
list.add(it)
|
||||
}
|
||||
if (container.byte!!.toInt() == '/'.code) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
container.objects = list
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readFinishingTag(dis: InputStream, buffer: Container, tagName: String): Container =
|
||||
readExactly(dis, buffer, "</${tagName}>")
|
||||
|
||||
private fun readValue(dis: InputStream, buffer: Container, tagName: String): Container =
|
||||
readUntil(dis, buffer, "</$tagName>")
|
||||
|
||||
private fun readAttributes(dis: InputStream, buffer: Container): Container {
|
||||
if (buffer.byte!!.toInt() == '>'.code) {
|
||||
return Container.empty(buffer.position)
|
||||
}
|
||||
val attributes = HashMap<String, String>()
|
||||
var attributeContainer: Container = Container.empty(buffer.position)
|
||||
do {
|
||||
attributeContainer = readAttribute(dis, attributeContainer)
|
||||
attributes.putAll(attributeContainer.attributes)
|
||||
} while (attributeContainer.attributes.isNotEmpty() && attributeContainer.byte!!.toInt() != '>'.code)
|
||||
val result = Container.emptyWithAttributes(attributeContainer.position, attributes)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun readAttribute(dis: InputStream, buffer: Container): Container {
|
||||
val nameContainer = readUntil(dis, buffer, setOf(' ', '\n', '=', '\t'))
|
||||
val nameAsString = nameContainer.buffer.asString()
|
||||
val name = nameAsString.substring(0, nameAsString.length - 1)
|
||||
val equalsContainer = readUntil(dis, Container.empty(nameContainer.position), '"')
|
||||
val valueContainer = readUntil(dis, Container.empty(equalsContainer.position), '"')
|
||||
val valueAsString = valueContainer.buffer.asString()
|
||||
val value = valueAsString.substring(0, valueAsString.length - 1)
|
||||
val result = Container.empty(valueContainer.position)
|
||||
val closingContainer = readSkipping(dis, result, SKIPPING_SET)
|
||||
val nextBlockContainer = Container.of(closingContainer.byte!!, closingContainer.position)
|
||||
nextBlockContainer.attributes = mapOf(name to value)
|
||||
return nextBlockContainer
|
||||
}
|
||||
|
||||
private fun readUntil(dis: InputStream, buffer: Container, ch: Char): Container {
|
||||
var container = buffer
|
||||
do {
|
||||
container = readByte(dis, container)
|
||||
} while (container.byte!!.toInt() != ch.code)
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readUntil(dis: InputStream, buffer: Container, chs: Set<Char>): Container {
|
||||
val chars = chs.map { c -> c.code }
|
||||
var container = buffer
|
||||
do {
|
||||
container = readByte(dis, container)
|
||||
} while (!chars.contains(container.byte!!.toInt()))
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readUntil(dis: InputStream, buffer: Container, charSequence: CharSequence): Container {
|
||||
var container = Container.of(buffer.byte!!, buffer.position)
|
||||
var pos = 0
|
||||
do {
|
||||
container = readByte(dis, container)
|
||||
if (container.byte!!.toInt() == charSequence[pos].code) {
|
||||
pos++
|
||||
} else {
|
||||
pos = 0
|
||||
}
|
||||
} while (pos < charSequence.length)
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readSkipping(dis: InputStream, buffer: Container, chs: Set<Char>): Container {
|
||||
val chars = chs.map { c -> c.code }
|
||||
var container = buffer
|
||||
do {
|
||||
container = readByte(dis, container)
|
||||
} while (chars.contains(container.byte!!.toInt()))
|
||||
return Container.of(container.byte!!, container.position)
|
||||
}
|
||||
|
||||
private fun readExactly(dis: InputStream, buffer: Container, charSequence: CharSequence): Container {
|
||||
var container = buffer
|
||||
do {
|
||||
container = readByte(dis, container)
|
||||
if (charSequence.substring(0, container.buffer.size) != container.buffer.asString()) {
|
||||
throw IllegalArgumentException("Expected closing tag $charSequence at position ${buffer.position}")
|
||||
}
|
||||
} while (container.buffer.size < charSequence.length)
|
||||
return container
|
||||
}
|
||||
|
||||
private fun readNotSpace(dis: InputStream, buffer: Container): Container {
|
||||
val container = readByte(dis, buffer)
|
||||
if (container.byte!!.toInt() == ' '.code
|
||||
|| container.byte.toInt() == '\n'.code
|
||||
) {
|
||||
return readByte(dis, container)
|
||||
} else {
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
private fun readByte(dis: InputStream, container: Container): Container {
|
||||
val ba = ByteArray(1);
|
||||
if (-1 == dis.read(ba, 0, 1)) {
|
||||
throw InterruptedException("EOF")
|
||||
} else if (ba.size != 1) {
|
||||
throw InterruptedException("Reading of 1 byte returns ${ba.size} bytes")
|
||||
}
|
||||
return Container(container.position + 1, ba[0], container.buffer.plus(ba))
|
||||
}
|
||||
|
||||
class Container(val position: Long = 0, val byte: Byte?, val buffer: ByteArray) {
|
||||
var objects: List<XmlObject>? = null
|
||||
var attributes: Map<String, String> = HashMap()
|
||||
var value: String? = null
|
||||
|
||||
companion object {
|
||||
fun empty(): Container = empty(0)
|
||||
fun empty(position: Long) = Container(position, null, ByteArray(0))
|
||||
fun emptyWithAttributes(position: Long, attributes: Map<String, String>): Container {
|
||||
val container = Container.empty(position)
|
||||
container.attributes = attributes
|
||||
return container
|
||||
}
|
||||
|
||||
fun of(b: Byte, position: Long) = Container(position, b, ByteArray(1) { _ -> b })
|
||||
}
|
||||
|
||||
override fun toString(): String = this.buffer.asString()
|
||||
}
|
||||
|
||||
class XmlObject(type: String) {
|
||||
val type: String
|
||||
var attributes: Map<String, String> = HashMap()
|
||||
var nested: List<XmlObject>? = null
|
||||
var value: String? = null
|
||||
|
||||
init {
|
||||
this.type = type.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SKIPPING_SET = setOf(' ', '\n', '\r', '\t')
|
||||
}
|
||||
}
|
@ -77,6 +77,8 @@ limitations under the License.
|
||||
|
||||
package me.bvn13.sdk.android.gpx
|
||||
|
||||
import me.bvn13.sdk.android.gpx.GpxConstant.Companion.VERSION
|
||||
|
||||
/**
|
||||
* GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements to the extensions section of the GPX document.
|
||||
*
|
||||
@ -100,5 +102,36 @@ class GpxType(
|
||||
val trk: List<TrkType>? = null,
|
||||
val extensions: ExtensionType? = null
|
||||
) {
|
||||
val version: String = "1.1"
|
||||
val version: String = VERSION
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as GpxType
|
||||
|
||||
if (metadata != other.metadata) return false
|
||||
if (creator != other.creator) return false
|
||||
if (wpt != other.wpt) return false
|
||||
if (rte != other.rte) return false
|
||||
if (trk != other.trk) return false
|
||||
if (extensions != other.extensions) return false
|
||||
if (version != other.version) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = metadata.hashCode()
|
||||
result = 31 * result + creator.hashCode()
|
||||
result = 31 * result + (wpt?.hashCode() ?: 0)
|
||||
result = 31 * result + (rte?.hashCode() ?: 0)
|
||||
result = 31 * result + (trk?.hashCode() ?: 0)
|
||||
result = 31 * result + (extensions?.hashCode() ?: 0)
|
||||
result = 31 * result + version.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
companion object { }
|
||||
}
|
||||
|
@ -77,15 +77,13 @@ limitations under the License.
|
||||
|
||||
package me.bvn13.sdk.android.gpx
|
||||
|
||||
import me.bvn13.sdk.android.gpx.GpxWriter.Companion.DTF
|
||||
import me.bvn13.sdk.android.gpx.GpxWriter.Companion.HEADER
|
||||
import me.bvn13.sdk.android.gpx.GpxConstant.Companion.DTF
|
||||
import me.bvn13.sdk.android.gpx.GpxConstant.Companion.HEADER
|
||||
import me.bvn13.sdk.android.gpx.GpxWriter.Companion.SCHEMA_LOCATION
|
||||
import me.bvn13.sdk.android.gpx.GpxWriter.Companion.XMLNS
|
||||
import me.bvn13.sdk.android.gpx.GpxWriter.Companion.XMLNS_XSI
|
||||
import java.time.Clock
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
|
||||
fun GpxType.toXmlString(): String = this.toXmlString(null)
|
||||
|
||||
@ -156,7 +154,7 @@ fun RteType.toXmlString() = """
|
||||
${toXmlString(this.number, "number")}
|
||||
${toXmlString(this.type, "type")}
|
||||
${this.extensions?.toXmlString() ?: ""}
|
||||
${this.rtept?.toXmlString() ?: ""}
|
||||
${this.rtept?.toXmlString("rtept") ?: ""}
|
||||
</rte>
|
||||
""".trim().removeEmptyStrings()
|
||||
|
||||
@ -262,14 +260,8 @@ private fun String.removeEmptyStrings() = this.lineSequence().map {
|
||||
|
||||
class GpxWriter {
|
||||
companion object {
|
||||
const val HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
const val XMLNS = "http://www.topografix.com/GPX/1/1"
|
||||
const val XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
const val SCHEMA_LOCATION = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
|
||||
internal val DTF =
|
||||
DateTimeFormatterBuilder().append(ISO_LOCAL_DATE_TIME) // use the existing formatter for date time
|
||||
.appendOffset("+HH:MM", "+00:00") // set 'noOffsetText' to desired '+00:00'
|
||||
.toFormatter()
|
||||
}
|
||||
}
|
||||
|
@ -87,4 +87,23 @@ package me.bvn13.sdk.android.gpx
|
||||
* [type] Mime type of content (image/jpeg)
|
||||
*/
|
||||
class LinkType(val href: String, val text: String? = null, val type: String? = null) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LinkType
|
||||
|
||||
if (href != other.href) return false
|
||||
if (text != other.text) return false
|
||||
if (type != other.type) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = href.hashCode()
|
||||
result = 31 * result + (text?.hashCode() ?: 0)
|
||||
result = 31 * result + (type?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -82,4 +82,25 @@ package me.bvn13.sdk.android.gpx
|
||||
* Providing rich, meaningful information about your GPX files allows others to search for and use your GPS data.
|
||||
*/
|
||||
class MetadataType(val name: String, val description: String = "", val authorName: String = "") {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MetadataType
|
||||
|
||||
if (name != other.name) return false
|
||||
if (description != other.description) return false
|
||||
if (authorName != other.authorName) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + description.hashCode()
|
||||
result = 31 * result + authorName.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -114,4 +114,38 @@ class RteType(
|
||||
"number must be non negative Integer"
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as RteType
|
||||
|
||||
if (name != other.name) return false
|
||||
if (cmt != other.cmt) return false
|
||||
if (desc != other.desc) return false
|
||||
if (src != other.src) return false
|
||||
if (link != other.link) return false
|
||||
if (number != other.number) return false
|
||||
if (type != other.type) return false
|
||||
if (extensions != other.extensions) return false
|
||||
if (rtept != other.rtept) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name?.hashCode() ?: 0
|
||||
result = 31 * result + (cmt?.hashCode() ?: 0)
|
||||
result = 31 * result + (desc?.hashCode() ?: 0)
|
||||
result = 31 * result + (src?.hashCode() ?: 0)
|
||||
result = 31 * result + (link?.hashCode() ?: 0)
|
||||
result = 31 * result + (number ?: 0)
|
||||
result = 31 * result + (type?.hashCode() ?: 0)
|
||||
result = 31 * result + (extensions?.hashCode() ?: 0)
|
||||
result = 31 * result + (rtept?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -116,4 +116,38 @@ class TrkType(
|
||||
"number must be non negative Integer"
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TrkType
|
||||
|
||||
if (name != other.name) return false
|
||||
if (cmt != other.cmt) return false
|
||||
if (desc != other.desc) return false
|
||||
if (src != other.src) return false
|
||||
if (link != other.link) return false
|
||||
if (number != other.number) return false
|
||||
if (type != other.type) return false
|
||||
if (extensions != other.extensions) return false
|
||||
if (trkseg != other.trkseg) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name?.hashCode() ?: 0
|
||||
result = 31 * result + (cmt?.hashCode() ?: 0)
|
||||
result = 31 * result + (desc?.hashCode() ?: 0)
|
||||
result = 31 * result + (src?.hashCode() ?: 0)
|
||||
result = 31 * result + (link?.hashCode() ?: 0)
|
||||
result = 31 * result + (number ?: 0)
|
||||
result = 31 * result + (type?.hashCode() ?: 0)
|
||||
result = 31 * result + (extensions?.hashCode() ?: 0)
|
||||
result = 31 * result + (trkseg?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -88,4 +88,21 @@ class TrksegType(
|
||||
val trkpt: List<WptType>? = null,
|
||||
val extensions: ExtensionType? = null
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TrksegType
|
||||
|
||||
if (trkpt != other.trkpt) return false
|
||||
if (extensions != other.extensions) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = trkpt?.hashCode() ?: 0
|
||||
result = 31 * result + (extensions?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -162,4 +162,62 @@ class WptType(
|
||||
"dgpsid must be in 0..1023"
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as WptType
|
||||
|
||||
if (lat != other.lat) return false
|
||||
if (lon != other.lon) return false
|
||||
if (ele != other.ele) return false
|
||||
if (time != other.time) return false
|
||||
if (magvar != other.magvar) return false
|
||||
if (geoidheight != other.geoidheight) return false
|
||||
if (name != other.name) return false
|
||||
if (cmt != other.cmt) return false
|
||||
if (desc != other.desc) return false
|
||||
if (src != other.src) return false
|
||||
if (link != other.link) return false
|
||||
if (sym != other.sym) return false
|
||||
if (type != other.type) return false
|
||||
if (fix != other.fix) return false
|
||||
if (sat != other.sat) return false
|
||||
if (hdop != other.hdop) return false
|
||||
if (vdop != other.vdop) return false
|
||||
if (pdop != other.pdop) return false
|
||||
if (ageofgpsdata != other.ageofgpsdata) return false
|
||||
if (dgpsid != other.dgpsid) return false
|
||||
if (extensions != other.extensions) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = lat.hashCode()
|
||||
result = 31 * result + lon.hashCode()
|
||||
result = 31 * result + (ele?.hashCode() ?: 0)
|
||||
result = 31 * result + (time?.hashCode() ?: 0)
|
||||
result = 31 * result + (magvar?.hashCode() ?: 0)
|
||||
result = 31 * result + (geoidheight?.hashCode() ?: 0)
|
||||
result = 31 * result + (name?.hashCode() ?: 0)
|
||||
result = 31 * result + (cmt?.hashCode() ?: 0)
|
||||
result = 31 * result + (desc?.hashCode() ?: 0)
|
||||
result = 31 * result + (src?.hashCode() ?: 0)
|
||||
result = 31 * result + (link?.hashCode() ?: 0)
|
||||
result = 31 * result + (sym?.hashCode() ?: 0)
|
||||
result = 31 * result + (type?.hashCode() ?: 0)
|
||||
result = 31 * result + (fix?.hashCode() ?: 0)
|
||||
result = 31 * result + (sat ?: 0)
|
||||
result = 31 * result + (hdop?.hashCode() ?: 0)
|
||||
result = 31 * result + (vdop?.hashCode() ?: 0)
|
||||
result = 31 * result + (pdop?.hashCode() ?: 0)
|
||||
result = 31 * result + (ageofgpsdata ?: 0)
|
||||
result = 31 * result + (dgpsid ?: 0)
|
||||
result = 31 * result + (extensions?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
412
src/test/kotlin/me/bvn13/sdk/android/gpx/GpxReaderTest.kt
Normal file
412
src/test/kotlin/me/bvn13/sdk/android/gpx/GpxReaderTest.kt
Normal file
@ -0,0 +1,412 @@
|
||||
package me.bvn13.sdk.android.gpx
|
||||
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GpxReaderTest {
|
||||
|
||||
@DisplayName("test GPX Reader")
|
||||
@Test
|
||||
fun testReader() {
|
||||
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 gpxString = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx
|
||||
xmlns="http://www.topografix.com/GPX/1/1"
|
||||
version="1.1"
|
||||
creator="me.bvn13.sdk.android.gpx"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<metadata>
|
||||
<name>test name</name>
|
||||
<desc>test description</desc>
|
||||
<author>
|
||||
<name>bvn13</name>
|
||||
</author>
|
||||
</metadata>
|
||||
<wpt lat="14.64736838389662" lon="7.93212890625">
|
||||
<ele>10.0</ele>
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<magvar>3.0</magvar>
|
||||
<geoidheight>45.0</geoidheight>
|
||||
<name>test point 1</name>
|
||||
<cmt>comment 1</cmt>
|
||||
<desc>description of point 1</desc>
|
||||
<src>source 1</src>
|
||||
<link href="http://link-to.site.href">
|
||||
<text>text</text>
|
||||
<type>hyperlink</type>
|
||||
</link>
|
||||
<link href="http://link2-to.site.href">
|
||||
<text>text2</text>
|
||||
<type>hyperlink2</type>
|
||||
</link>
|
||||
<sym>sym 1</sym>
|
||||
<type>type 1</type>
|
||||
<fix>dgps</fix>
|
||||
<sat>1</sat>
|
||||
<hdop>55.0</hdop>
|
||||
<vdop>66.0</vdop>
|
||||
<pdop>77.0</pdop>
|
||||
<ageofgpsdata>44</ageofgpsdata>
|
||||
<dgpsid>88</dgpsid>
|
||||
<extensions>
|
||||
<extension1 first="second" third="fours"></extension1>
|
||||
<extension2 aa="bb" cc="dd"></extension2>
|
||||
</extensions>
|
||||
</wpt>
|
||||
<rte>
|
||||
<name>rte name</name>
|
||||
<cmt>cmt</cmt>
|
||||
<desc>desc</desc>
|
||||
<src>src</src>
|
||||
<link href="https://new.link.rte">
|
||||
<text>new text rte</text>
|
||||
<type>hyperlink</type>
|
||||
</link>
|
||||
<number>1234</number>
|
||||
<type>route</type>
|
||||
<extensions>
|
||||
<ext-1>value1</ext-1>
|
||||
</extensions>
|
||||
<rtept lat="14.64736838389662" lon="7.93212890625">
|
||||
<ele>10.0</ele>
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<magvar>3.0</magvar>
|
||||
<geoidheight>45.0</geoidheight>
|
||||
<name>test point 1</name>
|
||||
<cmt>comment 1</cmt>
|
||||
<desc>description of point 1</desc>
|
||||
<src>source 1</src>
|
||||
<link href="http://link-to.site.href">
|
||||
<text>text</text>
|
||||
<type>hyperlink</type>
|
||||
</link>
|
||||
<link href="http://link2-to.site.href">
|
||||
<text>text2</text>
|
||||
<type>hyperlink2</type>
|
||||
</link>
|
||||
<sym>sym 1</sym>
|
||||
<type>type 1</type>
|
||||
<fix>dgps</fix>
|
||||
<sat>1</sat>
|
||||
<hdop>55.0</hdop>
|
||||
<vdop>66.0</vdop>
|
||||
<pdop>77.0</pdop>
|
||||
<ageofgpsdata>44</ageofgpsdata>
|
||||
<dgpsid>88</dgpsid>
|
||||
<extensions>
|
||||
<extension1 first="second" third="fours"></extension1>
|
||||
<extension2 aa="bb" cc="dd"></extension2>
|
||||
</extensions>
|
||||
</rtept>
|
||||
</rte>
|
||||
<trk>
|
||||
<name>track 1</name>
|
||||
<cmt>comment track 1</cmt>
|
||||
<desc>desc track 1</desc>
|
||||
<src>src track 1</src>
|
||||
<number>1234</number>
|
||||
<type>type 1</type>
|
||||
<trkseg>
|
||||
<trkpt lat="14.64736838389662" lon="7.93212890625">
|
||||
<ele>10.0</ele>
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<magvar>3.0</magvar>
|
||||
<geoidheight>45.0</geoidheight>
|
||||
<name>test point 1</name>
|
||||
<cmt>comment 1</cmt>
|
||||
<desc>description of point 1</desc>
|
||||
<src>source 1</src>
|
||||
<link href="http://link-to.site.href">
|
||||
<text>text</text>
|
||||
<type>hyperlink</type>
|
||||
</link>
|
||||
<link href="http://link2-to.site.href">
|
||||
<text>text2</text>
|
||||
<type>hyperlink2</type>
|
||||
</link>
|
||||
<sym>sym 1</sym>
|
||||
<type>type 1</type>
|
||||
<fix>dgps</fix>
|
||||
<sat>1</sat>
|
||||
<hdop>55.0</hdop>
|
||||
<vdop>66.0</vdop>
|
||||
<pdop>77.0</pdop>
|
||||
<ageofgpsdata>44</ageofgpsdata>
|
||||
<dgpsid>88</dgpsid>
|
||||
<extensions>
|
||||
<extension1 first="second" third="fours"></extension1>
|
||||
<extension2 aa="bb" cc="dd"></extension2>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="14.64736838389662" lon="7.93212890625">
|
||||
<ele>10.0</ele>
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<magvar>3.0</magvar>
|
||||
<geoidheight>45.0</geoidheight>
|
||||
<name>test point 1</name>
|
||||
<cmt>comment 1</cmt>
|
||||
<desc>description of point 1</desc>
|
||||
<src>source 1</src>
|
||||
<link href="http://link-to.site.href">
|
||||
<text>text</text>
|
||||
<type>hyperlink</type>
|
||||
</link>
|
||||
<link href="http://link2-to.site.href">
|
||||
<text>text2</text>
|
||||
<type>hyperlink2</type>
|
||||
</link>
|
||||
<sym>sym 1</sym>
|
||||
<type>type 1</type>
|
||||
<fix>dgps</fix>
|
||||
<sat>1</sat>
|
||||
<hdop>55.0</hdop>
|
||||
<vdop>66.0</vdop>
|
||||
<pdop>77.0</pdop>
|
||||
<ageofgpsdata>44</ageofgpsdata>
|
||||
<dgpsid>88</dgpsid>
|
||||
<extensions>
|
||||
<extension1 first="second" third="fours"></extension1>
|
||||
<extension2 aa="bb" cc="dd"></extension2>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
""".trim()
|
||||
.lineSequence()
|
||||
.map {
|
||||
it.trim()
|
||||
}
|
||||
.joinToString("\n")
|
||||
|
||||
val gpx = GpxType.read(gpxString.byteInputStream())
|
||||
assertEquals(gpxType, gpx)
|
||||
}
|
||||
}
|
@ -146,7 +146,7 @@ class GpxWriterTest {
|
||||
|
||||
@DisplayName("test maximum")
|
||||
@Test
|
||||
fun maximumTest() {
|
||||
fun testMaximum() {
|
||||
val clock = Clock.fixed(
|
||||
LocalDateTime.of(2022, 9, 24, 15, 4, 0, 0).toInstant(ZoneOffset.ofHours(3)), ZoneId.of("Europe/Moscow")
|
||||
)
|
||||
@ -432,7 +432,7 @@ class GpxWriterTest {
|
||||
<extensions>
|
||||
<ext-1>value1</ext-1>
|
||||
</extensions>
|
||||
<wpt lat="14.64736838389662" lon="7.93212890625">
|
||||
<rtept lat="14.64736838389662" lon="7.93212890625">
|
||||
<ele>10.0</ele>
|
||||
<time>2022-09-24T15:04:00+03:00</time>
|
||||
<magvar>3.0</magvar>
|
||||
@ -462,7 +462,7 @@ class GpxWriterTest {
|
||||
<extension1 first="second" third="fours"></extension1>
|
||||
<extension2 aa="bb" cc="dd"></extension2>
|
||||
</extensions>
|
||||
</wpt>
|
||||
</rtept>
|
||||
</rte>
|
||||
<trk>
|
||||
<name>track 1</name>
|
||||
|
Loading…
Reference in New Issue
Block a user