Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 202 additions & 1 deletion api/android-sdk-core.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.mapconductor.core.map

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
* The parts of [StyleMutation] that need a real `org.json`, which the JVM
* unit tests do not have: the stub in the Android jar throws on every call,
* so reading what the style compiler wrote has to be checked on a device.
*/
@RunWith(AndroidJUnit4::class)
class StyleMutationParseTest {
@Test
fun readsWhatTheCompilerWrites() {
val parsed =
StyleMutation.parseList(
"""
[
{"op":"setPaint","layerId":"roads","key":"line-color","value":"#fff","previous":null},
{"op":"setLayout","layerId":"labels","key":"visibility","value":"none","previous":"visible"},
{"op":"setFilter","layerId":"roads","value":["==","class","motorway"],"previous":null},
{"op":"setZoomRange","layerId":"roads","minZoom":5,"maxZoom":null,
"previousMinZoom":null,"previousMaxZoom":null},
{"op":"addLayer","layerId":"bg","layer":{"id":"bg","type":"background"},"beforeId":"water"},
{"op":"somethingFromTheFuture","layerId":"roads"}
]
""".trimIndent(),
)

assertEquals("the unknown op is dropped, the rest survive", 5, parsed.size)
// A string value keeps its quotes, or no renderer could read it back.
assertEquals("\"#fff\"", (parsed[0] as StyleMutation.SetPaint).value)
assertEquals("null", (parsed[0] as StyleMutation.SetPaint).previous)
assertEquals("\"none\"", (parsed[1] as StyleMutation.SetLayout).value)
assertEquals("[\"==\",\"class\",\"motorway\"]", (parsed[2] as StyleMutation.SetFilter).value)
assertEquals(5.0, (parsed[3] as StyleMutation.SetZoomRange).minZoom)
assertEquals(null, (parsed[3] as StyleMutation.SetZoomRange).maxZoom)
assertEquals("bg", parsed[4].layerId)
assertEquals("water", (parsed[4] as StyleMutation.AddLayer).beforeId)
}

/** Rubbish in must not take the map down; it means no adjustment. */
@Test
fun answersWithNothingRatherThanThrowing() {
assertEquals(emptyList<StyleMutation>(), StyleMutation.parseList("not json"))
assertEquals(emptyList<StyleMutation>(), StyleMutation.parseList("{}"))
assertEquals(emptyList<StyleMutation>(), StyleMutation.parseList("[{\"op\":\"setPaint\"}]"))
}

@Test
fun addingALayerIsUndoneByRemovingIt() {
val add = StyleMutation.AddLayer("bg", """{"id":"bg","type":"background"}""", beforeId = null)
assertEquals(StyleMutation.RemoveLayer("bg"), add.reversed())
}

/**
* Applying a set and then its reverse has to leave the map where it
* started, whatever the values were.
*/
@Test
fun everyMutationUndoesItself() {
val mutations =
StyleMutation.parseList(
"""
[
{"op":"setPaint","layerId":"a","key":"line-color","value":"#fff","previous":"#888"},
{"op":"setLayout","layerId":"b","key":"visibility","value":"none","previous":null},
{"op":"setFilter","layerId":"c","value":["all"],"previous":null},
{"op":"setZoomRange","layerId":"d","minZoom":5,"maxZoom":12,
"previousMinZoom":null,"previousMaxZoom":22}
]
""".trimIndent(),
)
for (mutation in mutations) {
val there = mutation.reversed()!!
val andBack = there.reversed()!!
assertEquals("${mutation.target} did not survive a round trip", mutation, andBack)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class MarkerTileRenderBenchmark {
private fun manager(): MarkerManager<Unit> {
val manager = MarkerManager.defaultManager<Unit>(minMarkerCount = 1)
var seed = 12345uL

fun next(): Double {
seed = seed * 6364136223846793005uL + 1442695040888963407uL
return (seed shr 11).toDouble() / (1uL shl 53).toDouble()
Expand Down Expand Up @@ -52,10 +53,11 @@ class MarkerTileRenderBenchmark {
// The tile column/row central Tokyo lands on at this zoom.
val n = 1 shl z
val centerX = ((139.75 + 180.0) / 360.0 * n).toInt()
val centerY = run {
val latRad = Math.toRadians(35.69)
((1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n).toInt()
}
val centerY =
run {
val latRad = Math.toRadians(35.69)
((1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n).toInt()
}
var totalMs = 0L
var tiles = 0
for (dx in -1..1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class MarkerTileSeamDeviceTest {
private fun manager(): MarkerManager<Unit> {
val manager = MarkerManager.defaultManager<Unit>(minMarkerCount = 1)
var seed = 12345uL

fun next(): Double {
seed = seed * 6364136223846793005uL + 1442695040888963407uL
return (seed shr 11).toDouble() / (1uL shl 53).toDouble()
Expand All @@ -53,6 +54,49 @@ class MarkerTileSeamDeviceTest {
return manager
}

@Test
fun distinctRectanglesWithMatchingLowBitsAreAllRendered() {
val manager = MarkerManager.defaultManager<Unit>(minMarkerCount = 1)
val icon =
ImageIcon(
image = android.graphics.drawable.ColorDrawable(android.graphics.Color.RED),
iconSize =
androidx.compose.ui.unit
.Dp(16f),
)
val tilePixels =
com.mapconductor.core.ResourceProvider
.dpToPx(512.0)
.toInt()
val positions = listOf(100 to 100, 200 to 100, 100 to 356)
for ((index, point) in positions.withIndex()) {
val worldX = 8.0 + point.first.toDouble() / tilePixels
val worldY = 6.0 + point.second.toDouble() / tilePixels
val latitude = Math.toDegrees(Math.atan(Math.sinh(Math.PI * (1.0 - 2.0 * worldY / 16.0))))
manager.registerEntity(
MarkerEntity(
marker = null,
state =
MarkerState(
position = GeoPoint.fromLatLong(latitude, worldX * 360.0 / 16.0 - 180.0),
id = "rectangle-$index",
icon = icon,
),
visible = true,
isRendered = true,
tiling = true,
),
)
}
val renderer = MarkerTileRenderer(manager, tileSize = 512, cacheSizeBytes = 1)
val bytes = renderer.renderTile(TileRequest(x = 8, y = 6, z = 4))!!
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
for ((x, y) in positions) {
assertEquals("marker at $x,$y", android.graphics.Color.RED, bitmap.getPixel(x, y))
}
bitmap.recycle()
}

@Test
fun adjacentTilesAgreeAtEveryVerticalSeam() {
val renderer =
Expand All @@ -70,10 +114,11 @@ class MarkerTileSeamDeviceTest {
for (z in intArrayOf(12, 13, 14, 15, 16, 17)) {
val n = 1 shl z
val centerX = ((139.75 + 180.0) / 360.0 * n).toInt()
val centerY = run {
val latRad = Math.toRadians(35.69)
((1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n).toInt()
}
val centerY =
run {
val latRad = Math.toRadians(35.69)
((1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n).toInt()
}
for (dx in -3..2) {
for (dy in -2..2) {
val at = renderer.renderTile(TileRequest(x = centerX + dx, y = centerY + dy, z = z))
Expand All @@ -91,22 +136,34 @@ class MarkerTileSeamDeviceTest {
for (y in 0 until tile.height) {
val a = (tile.getPixel(tile.width - 1, y) ushr 24) > 0x20
val b = (eastTile.getPixel(0, y) ushr 24) > 0x20
if (a != b) { run += 1; if (run > worst) worst = run } else run = 0
if (a != b) {
run += 1
if (run > worst) worst = run
} else {
run = 0
}
}
if (worst > 16) {
bad += 1
Log.i(TAG, "SEAM broken(vertical) z=$z x=${centerX + dx}/${centerX + dx + 1} y=${centerY + dy} worstRun=$worst")
Log
.i(TAG, "SEAM broken(vertical) z=$z x=${centerX + dx}/${centerX + dx + 1} y=${centerY + dy} worstRun=$worst")
}
worst = 0
run = 0
for (x in 0 until tile.width) {
val a = (tile.getPixel(x, tile.height - 1) ushr 24) > 0x20
val b = (southTile.getPixel(x, 0) ushr 24) > 0x20
if (a != b) { run += 1; if (run > worst) worst = run } else run = 0
if (a != b) {
run += 1
if (run > worst) worst = run
} else {
run = 0
}
}
if (worst > 16) {
bad += 1
Log.i(TAG, "SEAM broken(horizontal) z=$z x=${centerX + dx} y=${centerY + dy}/${centerY + dy + 1} worstRun=$worst")
Log
.i(TAG, "SEAM broken(horizontal) z=$z x=${centerX + dx} y=${centerY + dy}/${centerY + dy + 1} worstRun=$worst")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.mapconductor.core.tileserver

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.abs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.abs
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color

/**
* On-device checks for the Rust PNG encoder.
Expand All @@ -25,7 +25,6 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidJUnit4::class)
class TilePngEncoderTest {

/**
* The whole point of putting the library in the core: it has to load in an
* ordinary consumer process, with no help from the caller.
Expand Down Expand Up @@ -90,11 +89,12 @@ class TilePngEncoderTest {
// Fully transparent pixels carry no recoverable colour: both
// encoders are free to write anything under alpha 0.
if (Color.alpha(b) == 0) continue
val step = maxOf(
abs(Color.red(a) - Color.red(b)),
abs(Color.green(a) - Color.green(b)),
abs(Color.blue(a) - Color.blue(b)),
)
val step =
maxOf(
abs(Color.red(a) - Color.red(b)),
abs(Color.green(a) - Color.green(b)),
abs(Color.blue(a) - Color.blue(b)),
)
assertTrue("colour at ($x,$y) differs by $step", step <= 1)
worstColourStep = maxOf(worstColourStep, step)
}
Expand Down Expand Up @@ -191,12 +191,13 @@ class TilePngEncoderTest {
for (index in pixels.indices) {
state = state * 1_664_525 + 1_013_904_223
val bits = state ushr 8
pixels[index] = Color.argb(
128 + (bits and 0x7F),
(bits ushr 7) and 0xFF,
(bits ushr 15) and 0xFF,
(bits ushr 23) and 0xFF,
)
pixels[index] =
Color.argb(
128 + (bits and 0x7F),
(bits ushr 7) and 0xFF,
(bits ushr 15) and 0xFF,
(bits ushr 23) and 0xFF,
)
}
bitmap.setPixels(pixels, 0, TILE, 0, 0, TILE, TILE)
return bitmap
Expand Down
Loading
Loading