diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 57707596c..38115fd2f 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,7 +2,7 @@ name: android on: pull_request: - branches: [ main, develop ] + branches: [ main, develop, release/** ] jobs: build: @@ -19,7 +19,7 @@ jobs: java-version: 17 - name: Build with Gradle env: - UBIQUE_ARTIFACTORY_URL: ${{secrets.UBIQUE_ARTIFACTORY_URL}} - UBIQUE_ARTIFACTORY_USER: ${{secrets.UBIQUE_ARTIFACTORY_USER}} - UBIQUE_ARTIFACTORY_PASS: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} + UB_ARTIFACTORY_URL_ANDROID: ${{secrets.UBIQUE_ARTIFACTORY_URL}} + UB_ARTIFACTORY_USERNAME: ${{secrets.UBIQUE_ARTIFACTORY_USER}} + UB_ARTIFACTORY_PASSWORD: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} run: cd android; ./gradlew assembleDebug -P'android.injected.build.abi=arm64-v8a' diff --git a/.github/workflows/sonatype-develop.yml b/.github/workflows/sonatype-develop.yml deleted file mode 100644 index 5e6c612b3..000000000 --- a/.github/workflows/sonatype-develop.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Upload AAR to Sonatype Nexus - -on: - push: - branches: [ develop ] - -jobs: - build: - name: "Upload AAR to Sonatype Nexus" - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - distribution: zulu - java-version: 17 - - name: Set Build Variables - id: vars - run: | - artifactId=$(sed -n -e 's/^POM_ARTIFACT_ID=//p' android/gradle.properties)-dev - echo ::set-output name=artifactId::$artifactId - version=$(sed -n -e 's/^VERSION_NAME=//p' android/gradle.properties).$GITHUB_RUN_NUMBER - echo ::set-output name=version::$version - - name: Upload - env: - SIGNING_KEY_ARMOR: ${{secrets.MAVEN_SIGNING_KEY_ARMOR_BASE64}} - SIGNING_KEY_ID: ${{secrets.MAVEN_SIGNING_KEY_ID}} - SIGNING_KEY_PASSWORD: ${{secrets.MAVEN_SIGNING_KEY_PASSPHRASE}} - UBIQUE_ARTIFACTORY_URL: ${{secrets.UBIQUE_ARTIFACTORY_URL}} - UBIQUE_ARTIFACTORY_USER: ${{secrets.UBIQUE_ARTIFACTORY_USER}} - UBIQUE_ARTIFACTORY_PASS: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} - run: | - echo "Replacing original version name with dev version name" - sed -i "/^VERSION_NAME=/s/.*/VERSION_NAME=${{ steps.vars.outputs.version }}/" android/gradle.properties - - echo "Replacing original artifact ID with dev artifact ID" - sed -i "/^POM_ARTIFACT_ID=/s/.*/POM_ARTIFACT_ID=${{ steps.vars.outputs.artifactId }}/" android/gradle.properties - - echo "Create .gpg key from secret" - echo $SIGNING_KEY_ARMOR | base64 --decode > ./signingkey.asc - gpg --quiet --output $GITHUB_WORKSPACE/signingkey.gpg --dearmor ./signingkey.asc - - cd android - ./gradlew publishAllPublicationsToUbiqueMavenRepository -Psigning.secretKeyRingFile=$GITHUB_WORKSPACE/signingkey.gpg -Psigning.password=$SIGNING_KEY_PASSWORD -Psigning.keyId=$SIGNING_KEY_ID - echo "### Maven Dependency" >> $GITHUB_STEP_SUMMARY - echo "| Git Commit | Artifact ID | Maven Version |" >> $GITHUB_STEP_SUMMARY - echo "| --- | --- | --- |" >> $GITHUB_STEP_SUMMARY - echo "| $GITHUB_SHA | ${{ steps.vars.outputs.artifactId }} | ${{ steps.vars.outputs.version }} |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/sonatype.yml b/.github/workflows/sonatype.yml index abf077719..5aafa55e5 100644 --- a/.github/workflows/sonatype.yml +++ b/.github/workflows/sonatype.yml @@ -31,9 +31,9 @@ jobs: SIGNING_KEY_ARMOR: ${{secrets.MAVEN_SIGNING_KEY_ARMOR_BASE64}} SIGNING_KEY_ID: ${{secrets.MAVEN_SIGNING_KEY_ID}} SIGNING_KEY_PASSWORD: ${{secrets.MAVEN_SIGNING_KEY_PASSPHRASE}} - UBIQUE_ARTIFACTORY_URL: ${{secrets.UBIQUE_ARTIFACTORY_URL}} - UBIQUE_ARTIFACTORY_USER: ${{secrets.UBIQUE_ARTIFACTORY_USER}} - UBIQUE_ARTIFACTORY_PASS: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} + UB_ARTIFACTORY_URL_ANDROID: ${{secrets.UBIQUE_ARTIFACTORY_URL}} + UB_ARTIFACTORY_USERNAME: ${{secrets.UBIQUE_ARTIFACTORY_USER}} + UB_ARTIFACTORY_PASSWORD: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} run: | echo "Create .gpg key from secret" echo $SIGNING_KEY_ARMOR | base64 --decode > ./signingkey.asc @@ -51,9 +51,9 @@ jobs: SIGNING_KEY_PASSWORD: ${{secrets.MAVEN_SIGNING_KEY_PASSPHRASE}} SONATYPE_NEXUS_USERNAME: ${{secrets.SONATYPE_NEXUS_USERNAME}} SONATYPE_NEXUS_PASSWORD: ${{secrets.SONATYPE_NEXUS_PASSWORD}} - UBIQUE_ARTIFACTORY_URL: ${{secrets.UBIQUE_ARTIFACTORY_URL}} - UBIQUE_ARTIFACTORY_USER: ${{secrets.UBIQUE_ARTIFACTORY_USER}} - UBIQUE_ARTIFACTORY_PASS: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} + UB_ARTIFACTORY_URL_ANDROID: ${{secrets.UBIQUE_ARTIFACTORY_URL}} + UB_ARTIFACTORY_USERNAME: ${{secrets.UBIQUE_ARTIFACTORY_USER}} + UB_ARTIFACTORY_PASSWORD: ${{secrets.UBIQUE_ARTIFACTORY_PASS}} run: | cd android echo "### Maven Dependency - Maven Central" >> $GITHUB_STEP_SUMMARY diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1ed5b51..089214587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog for Open Mobile Maps + +## Version 2.2.0 +- Adds support for `{bbox-epsg-3857}` placeholder in URL template +- Improves the handling of non-interactive polygon layers +- Improves performance logger +- Improves loading behavior of paused vector layer +- Pauses iOS MapView when in background +- Improves the stability of geojson parsing +- Fixes the interpolation for non-numeric values in vector layer styles +- Fixes a bug caused by an ill-timed vector layer reload +- Fixes issues with the zoom during moveToCenter animations + ## Version 2.1.0 (08.04.2024) - Adds dotted line style (line-dotted boolean and an optional skewFactor, defaulting to 1.0 and denoting the stretch factor) - Fixes style for short dashes @@ -7,12 +19,21 @@ - Fix map description issues in concurrent settings - Added a Helper to create geojson strings from GeoJsonPoints +## Version 2.0.8 (04.06.2024) +- Add edge fill options for OpenGL textures +- iOS instanced rendering alpha multiplication fix + +## Version 2.0.7 (29.04.2024) +- Adds performance logger (disabled by default) +- Fixes a bug in the android striped pattern shader +- Fixes a bug in geojson hole simplification + ## Version 2.0.6 (10.04.2024) -- log exception when a font can not be loaded +- Log exception when a font can not be loaded ## Version 2.0.5 (08.04.2024) - Expose thread_pool_callbacks in scheduler interface -- call onRemoved on layerInterface before the mapScene is destroyed +- Call onRemoved on layerInterface before the mapScene is destroyed ## Version 2.0.4 (22.03.2024) - Changes in symbol vector map collision detection: Run collision detection when zoom changes. This change ensures that icons never collide but also carries a performance burden. @@ -55,35 +76,35 @@ - Convenience function for RectCoord to PolygonCoord conversion ## Version 1.5.0 (23.03.2023) -- implements vector icon anchor -- evaluate color from feature context -- expose bounding box in djinni -- fixes touch propagation in VectorLayer -- iOS: fixes graphics object race conditions -- fixes vector tile origin -- adds option to enable/disable underzoom and overzoom -- adds exception logger -- adds network activity manager -- adds two finger zoom out gesture -- adds option to specify multiple loaders -- many improvements and bugfixes +- Implements vector icon anchor +- Evaluate color from feature context +- Expose bounding box in djinni +- Fixes touch propagation in VectorLayer +- iOS: fixes graphics object race conditions +- Fixes vector tile origin +- Adds option to enable/disable underzoom and overzoom +- Adds exception logger +- Adds network activity manager +- Adds two finger zoom out gesture +- Adds option to specify multiple loaders +- Many improvements and bugfixes ## Version 1.4.1 (16.08.2022) - iOS: updates device to ppi mapping - iOS: fixes masked line groups - iOS: fixes bug that caused the map to be cropped if display zoom is enabled - iOS: fixes metal crash when attempting to load empty texture -- fixes a error in MapCamera2d::getPaddingCorrectedBounds +- Fixes a error in MapCamera2d::getPaddingCorrectedBounds ## Version 1.4.0 (09.06.2022) - includes earcut.hpp dependency for polygon triangulation -- adds polygon and line group rendering objects which can be used to efficiently render many objects with a limited number of styles -- expands line style options -- adds masking methods to the layerInterface -- adds scissoring methods to the layerInterface -- adds option to display tiled raster layer density dependent -- adds off screen rendering helpers -- many improvements and bugfixes +- Adds polygon and line group rendering objects which can be used to efficiently render many objects with a limited number of styles +- Expands line style options +- Adds masking methods to the layerInterface +- Adds scissoring methods to the layerInterface +- Adds option to display tiled raster layer density dependent +- Adds off screen rendering helpers +- Many improvements and bugfixes ## Version 1.3.3 (16.08.2021) - Memory leak fix for Android @@ -97,21 +118,21 @@ - Add line layer implementation ## Version 1.3.0 (19.04.2021) -- fixes iOS retain cycle +- Fixes iOS retain cycle - Native library and relevant header files are now properly included in the published dependency - Added a Circle2dLayerObject -- bugfixes & improvements +- Bugfixes & improvements ## Version 1.2.0 (03.03.2021) - Add WMTS capability parsing capabilities - Add icon layer implementation -- implemented camera movement inertia -- improves animation handling -- bugfixes and improvements +- Implemented camera movement inertia +- Improves animation handling +- Bugfixes and improvements ## Version 1.1.0 (01.03.2021) -- various improvements & bugfixes +- Various improvements & bugfixes ## Version 1.0.0 (19.02.2021) -- initial version of open mobile maps +- Initial version of open mobile maps diff --git a/android/build.gradle b/android/build.gradle index aaba1a65f..4d5e891c1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -160,10 +160,10 @@ publishing { repositories { maven { name = "UbiqueMaven" - url = System.getenv('UBIQUE_ARTIFACTORY_URL') ?: readPropertyWithDefault('ubiqueArtifactoryUrl', '') + url = System.getenv('UB_ARTIFACTORY_URL_ANDROID') ?: readPropertyWithDefault('ubiqueMavenUrl', '') credentials { - username = System.getenv('UBIQUE_ARTIFACTORY_USER') ?: readPropertyWithDefault('ubiqueArtifactoryUser', '') - password = System.getenv('UBIQUE_ARTIFACTORY_PASS') ?: readPropertyWithDefault('ubiqueArtifactoryPass', '') + username = System.getenv('UB_ARTIFACTORY_USERNAME') ?: readPropertyWithDefault('ubiqueMavenUser', '') + password = System.getenv('UB_ARTIFACTORY_PASSWORD') ?: readPropertyWithDefault('ubiqueMavenPass', '') } } } diff --git a/android/gradle.properties b/android/gradle.properties index 9d33f25bd..911e2879a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -31,8 +31,8 @@ SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots/ GROUP=io.openmobilemaps POM_ARTIFACT_ID=mapscore -VERSION_NAME=2.1.0 -VERSION_CODE=2010000 +VERSION_NAME=2.2.0 +VERSION_CODE=2020000 POM_NAME=mapscore POM_PACKAGING=aar diff --git a/android/readme.md b/android/readme.md index 12b42c540..75ef0ddf6 100644 --- a/android/readme.md +++ b/android/readme.md @@ -65,7 +65,7 @@ This library is available on MavenCentral. To add it to your Android project, ad ``` dependencies { - implementation 'io.openmobilemaps:mapscore:2.1.0' + implementation 'io.openmobilemaps:mapscore:2.2.0' } ``` diff --git a/android/src/main/cpp/graphics/OpenGlContext.cpp b/android/src/main/cpp/graphics/OpenGlContext.cpp index 354d1ac40..d7f468d21 100644 --- a/android/src/main/cpp/graphics/OpenGlContext.cpp +++ b/android/src/main/cpp/graphics/OpenGlContext.cpp @@ -52,14 +52,14 @@ void OpenGlContext::setBackgroundColor(const Color &color) { backgroundColor = c void OpenGlContext::setupDrawFrame() { glClearColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a); - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClearStencil(0); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } void OpenGlContext::preRenderStencilMask() { glEnable(GL_STENCIL_TEST); - glClear(GL_STENCIL_BUFFER_BIT); glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_ALWAYS, 128, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } diff --git a/android/src/main/cpp/graphics/shader/BaseShaderProgramOpenGl.cpp b/android/src/main/cpp/graphics/shader/BaseShaderProgramOpenGl.cpp index e84a880f3..60dda9a9a 100644 --- a/android/src/main/cpp/graphics/shader/BaseShaderProgramOpenGl.cpp +++ b/android/src/main/cpp/graphics/shader/BaseShaderProgramOpenGl.cpp @@ -32,13 +32,14 @@ int BaseShaderProgramOpenGl::loadShader(int type, std::string shaderCode) { std::vector errorLog(maxLength); glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]); - LogError << "Shader " << shader << " failed:\n"; + std::stringstream errorSS; + errorSS << "Shader " << shader << " (" << getProgramName() << ") failed:\n"; for (auto a : errorLog) { - LogError << a; + errorSS << a; } - LogError <<= "."; + LogError << errorSS.str() <<= "."; } return shader; } diff --git a/android/src/main/cpp/graphics/shader/ColorPolygonGroup2dShaderOpenGl.cpp b/android/src/main/cpp/graphics/shader/ColorPolygonGroup2dShaderOpenGl.cpp index b56f068eb..b5b558e0e 100644 --- a/android/src/main/cpp/graphics/shader/ColorPolygonGroup2dShaderOpenGl.cpp +++ b/android/src/main/cpp/graphics/shader/ColorPolygonGroup2dShaderOpenGl.cpp @@ -94,7 +94,7 @@ std::string ColorPolygonGroup2dShaderOpenGl::getVertexShader() { } else if (styleIndex > numStyles) { styleIndex = numStyles; } - styleIndex = styleIndex * 5; + styleIndex = styleIndex * 7; color = vec4(polygonStyles[styleIndex], polygonStyles[styleIndex + 1], polygonStyles[styleIndex + 2], polygonStyles[styleIndex + 3] * polygonStyles[styleIndex + 4]); stripeInfo = vec2(polygonStyles[styleIndex + 5], polygonStyles[styleIndex + 6]); diff --git a/android/src/main/java/io/openmobilemaps/mapscore/graphics/BitmapTextureHolder.kt b/android/src/main/java/io/openmobilemaps/mapscore/graphics/BitmapTextureHolder.kt index 7d57d82ef..751b0aa79 100644 --- a/android/src/main/java/io/openmobilemaps/mapscore/graphics/BitmapTextureHolder.kt +++ b/android/src/main/java/io/openmobilemaps/mapscore/graphics/BitmapTextureHolder.kt @@ -12,17 +12,20 @@ package io.openmobilemaps.mapscore.graphics import android.graphics.Bitmap import android.graphics.Canvas +import android.graphics.Rect import android.graphics.drawable.Drawable import android.opengl.GLES20 import android.opengl.GLUtils import io.openmobilemaps.mapscore.shared.graphics.objects.TextureHolderInterface import java.nio.IntBuffer import java.util.concurrent.locks.ReentrantLock +import kotlin.math.min class BitmapTextureHolder( bitmap: Bitmap, private val minFilter: Int = GLES20.GL_LINEAR, private val magFilter: Int = GLES20.GL_LINEAR, + edgeFillMode: CanvasEdgeFillMode = CanvasEdgeFillMode.Mirorred ) : TextureHolderInterface() { val bitmap: Bitmap @@ -136,16 +139,46 @@ class BitmapTextureHolder( c.drawBitmap(bitmap, 0f, 0f, null) //Draw the picture mirrored again to fake clamp mode - c.save() - c.scale(1f, -1f, 0f, bitmap.height.toFloat()) - c.drawBitmap(bitmap, 0f, 0f, null) - c.restore() - c.save() - c.scale(-1f, 1f, bitmap.width.toFloat(), 0f) - c.drawBitmap(bitmap, 0f, 0f, null) - c.restore() - c.scale(-1f, -1f, bitmap.width.toFloat(), bitmap.height.toFloat()) - c.drawBitmap(bitmap, 0f, 0f, null) + when (edgeFillMode) { + is CanvasEdgeFillMode.Clamped -> { + c.drawBitmap( + bitmap, + Rect(0, bitmap.height - 1, bitmap.width, bitmap.height), + Rect(0, bitmap.height, bitmap.width, min(height, bitmap.height + edgeFillMode.borderWidthPx)), + null + ) + c.drawBitmap( + bitmap, + Rect(bitmap.width - 1, 0, bitmap.width, bitmap.height), + Rect(bitmap.width, 0, min(width, bitmap.width + edgeFillMode.borderWidthPx), bitmap.height), + null + ) + c.drawBitmap( + bitmap, + Rect(bitmap.width - 1, bitmap.height - 1, bitmap.width, bitmap.height), + Rect( + bitmap.width, + bitmap.height, + min(width, bitmap.width + edgeFillMode.borderWidthPx), + min(height, bitmap.height + edgeFillMode.borderWidthPx) + ), + null + ) + } + CanvasEdgeFillMode.Mirorred -> { + c.save() + c.scale(1f, -1f, 0f, bitmap.height.toFloat()) + c.drawBitmap(bitmap, 0f, 0f, null) + c.restore() + c.save() + c.scale(-1f, 1f, bitmap.width.toFloat(), 0f) + c.drawBitmap(bitmap, 0f, 0f, null) + c.restore() + c.scale(-1f, -1f, bitmap.width.toFloat(), bitmap.height.toFloat()) + c.drawBitmap(bitmap, 0f, 0f, null) + } + CanvasEdgeFillMode.None -> {} + } bitmap.recycle() bitmap = large } diff --git a/android/src/main/java/io/openmobilemaps/mapscore/graphics/CanvasEdgeFillMode.kt b/android/src/main/java/io/openmobilemaps/mapscore/graphics/CanvasEdgeFillMode.kt new file mode 100644 index 000000000..b4dc7a296 --- /dev/null +++ b/android/src/main/java/io/openmobilemaps/mapscore/graphics/CanvasEdgeFillMode.kt @@ -0,0 +1,7 @@ +package io.openmobilemaps.mapscore.graphics + +sealed class CanvasEdgeFillMode { + data object Mirorred : CanvasEdgeFillMode() + data class Clamped(val borderWidthPx: Int) : CanvasEdgeFillMode() + data object None : CanvasEdgeFillMode() +} \ No newline at end of file diff --git a/android/src/main/java/io/openmobilemaps/mapscore/graphics/GLThread.kt b/android/src/main/java/io/openmobilemaps/mapscore/graphics/GLThread.kt index 9ffe17bc4..9cbd4a5dd 100644 --- a/android/src/main/java/io/openmobilemaps/mapscore/graphics/GLThread.kt +++ b/android/src/main/java/io/openmobilemaps/mapscore/graphics/GLThread.kt @@ -77,6 +77,9 @@ class GLThread constructor( val lastDirtyTimestamp = AtomicLong(0) var hasFinishedSinceDirty = false + var enforcedFinishInterval: Int? = null + private var currentFrameIndex = 0 + var renderer: GLSurfaceView.Renderer? = null var surface: SurfaceTexture? = null @@ -171,6 +174,13 @@ class GLThread constructor( } } + enforcedFinishInterval?.let { interval -> + if (currentFrameIndex++ >= interval) { + GLES32.glFinish() + currentFrameIndex = 0 + } + } + onDrawCallback?.invoke() if (targetFrameRate > 0) { diff --git a/android/src/main/java/io/openmobilemaps/mapscore/graphics/GlTextureView.kt b/android/src/main/java/io/openmobilemaps/mapscore/graphics/GlTextureView.kt index 880de400e..9f15b2ff2 100644 --- a/android/src/main/java/io/openmobilemaps/mapscore/graphics/GlTextureView.kt +++ b/android/src/main/java/io/openmobilemaps/mapscore/graphics/GlTextureView.kt @@ -36,6 +36,7 @@ open class GlTextureView @JvmOverloads constructor(context: Context, attrs: Attr private val pendingTaskQueue = ArrayDeque Unit>>() private var pendingTargetFrameRate = -1 + private var pendingEnforcedFinishInterval: Int? = null private var shouldResume = false override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { @@ -47,6 +48,7 @@ open class GlTextureView @JvmOverloads constructor(context: Context, attrs: Attr useMSAA = this@GlTextureView.useMSAA renderer = this@GlTextureView.renderer targetFrameRate = pendingTargetFrameRate + enforcedFinishInterval = pendingEnforcedFinishInterval if (shouldResume) { doResume() } @@ -97,6 +99,11 @@ open class GlTextureView @JvmOverloads constructor(context: Context, attrs: Attr glThread?.targetFrameRate = frameRate } + fun setEnforcedFinishInterval(enforcedFinishInterval: Int?) { + pendingEnforcedFinishInterval = enforcedFinishInterval + glThread?.enforcedFinishInterval = enforcedFinishInterval + } + fun pauseGlThread() { shouldResume = false glThread?.doPause() diff --git a/android/src/main/java/io/openmobilemaps/mapscore/map/view/MapView.kt b/android/src/main/java/io/openmobilemaps/mapscore/map/view/MapView.kt index d097c9c6e..af27f1c13 100644 --- a/android/src/main/java/io/openmobilemaps/mapscore/map/view/MapView.kt +++ b/android/src/main/java/io/openmobilemaps/mapscore/map/view/MapView.kt @@ -72,13 +72,13 @@ open class MapView @JvmOverloads constructor(context: Context, attrs: AttributeS } override fun onMapResumed() { - mapViewStateMutable.value = MapViewState.RESUMED + updateMapViewState(MapViewState.RESUMED) } }) mapInterface.setBackgroundColor(Color(1f, 1f, 1f, 1f)) touchHandler = mapInterface.getTouchHandler() this.mapInterface = mapInterface - mapViewStateMutable.value = MapViewState.INITIALIZED + updateMapViewState(MapViewState.INITIALIZED) } fun registerLifecycle(lifecycle: Lifecycle) { @@ -120,7 +120,7 @@ open class MapView @JvmOverloads constructor(context: Context, attrs: AttributeS @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) open fun onDestroy() { lifecycle = null - mapViewStateMutable.value = MapViewState.DESTROYED + updateMapViewState(MapViewState.DESTROYED) finishGlThread() } @@ -132,7 +132,7 @@ open class MapView @JvmOverloads constructor(context: Context, attrs: AttributeS override fun onGlThreadPause() { if (mapViewStateMutable.value != MapViewState.PAUSED) { - mapViewStateMutable.value = MapViewState.PAUSED + updateMapViewState(MapViewState.PAUSED) requireMapInterface().pause() } } @@ -223,7 +223,6 @@ open class MapView @JvmOverloads constructor(context: Context, attrs: AttributeS requireMapInterface().removeLayer(layer.layerInterface()) } - override fun getCamera(): MapCamera2dInterface { return requireMapInterface().getCamera() } @@ -243,5 +242,12 @@ open class MapView @JvmOverloads constructor(context: Context, attrs: AttributeS SaveFrameUtil.saveCurrentFrame(Vec2I(width, height), spec, callback) } + private fun updateMapViewState(state: MapViewState) { + mapViewStateMutable.value = state + onMapViewStateUpdated(state) + } + + protected open fun onMapViewStateUpdated(state: MapViewState) {} + override fun requireMapInterface(): MapInterface = mapInterface ?: throw IllegalStateException("Map is not setup or already destroyed!") } \ No newline at end of file diff --git a/ios/graphics/Helpers/MTLDevice+Helpers.swift b/ios/graphics/Helpers/MTLDevice+Helpers.swift index 7bcf12f70..d34026ea4 100644 --- a/ios/graphics/Helpers/MTLDevice+Helpers.swift +++ b/ios/graphics/Helpers/MTLDevice+Helpers.swift @@ -29,6 +29,9 @@ extension MTLBuffer { self.contents().copyMemory(from: p, byteCount: Int(sharedBytes.elementCount * sharedBytes.bytesPerElement)) } } + func copyMemory(bytes pointer: UnsafeRawPointer, length: Int) { + self.contents().copyMemory(from: pointer, byteCount: length) + } } extension MTLBuffer? { @@ -44,4 +47,16 @@ extension MTLBuffer? { } } } + mutating public func copyOrCreate(bytes pointer: UnsafeRawPointer, length: Int, device: MTLDevice) { + switch self { + case .none: + self = device.makeBuffer(bytes: pointer, length: length) + case let .some(wrapped): + if wrapped.length == length { + wrapped.copyMemory(bytes: pointer, length: length) + } else { + self = device.makeBuffer(bytes: pointer, length: length) + } + } + } } diff --git a/ios/graphics/Model/Line/LineGroup2d.swift b/ios/graphics/Model/Line/LineGroup2d.swift index eceee5252..928e558d7 100644 --- a/ios/graphics/Model/Line/LineGroup2d.swift +++ b/ios/graphics/Model/Line/LineGroup2d.swift @@ -134,19 +134,16 @@ extension LineGroup2d: MCLineGroup2dInterface { return } - guard let verticesBuffer = device.makeBuffer(from: lines), - let indicesBuffer = device.makeBuffer(from: indices) - else { - fatalError("Cannot allocate buffers for LineGroup2d") - } - - verticesBuffer.label = "LineGroup2d.verticesBuffer" - indicesBuffer.label = "LineGroup2d.indicesBuffer" - lock.withCritical { - indicesCount = Int(indices.elementCount) - lineVerticesBuffer = verticesBuffer - lineIndicesBuffer = indicesBuffer + self.lineVerticesBuffer.copyOrCreate(from: lines, device: device) + self.lineIndicesBuffer.copyOrCreate(from: indices, device: device) + if self.lineVerticesBuffer != nil && self.lineIndicesBuffer != nil { + self.lineVerticesBuffer?.label = "LineGroup2d.verticesBuffer" + self.lineIndicesBuffer?.label = "LineGroup2d.indicesBuffer" + self.indicesCount = Int(indices.elementCount) + } else { + self.indicesCount = 0 + } } } diff --git a/ios/graphics/Model/Polygon/Polygon2d.swift b/ios/graphics/Model/Polygon/Polygon2d.swift index 356873bb2..0a4ddd9c6 100644 --- a/ios/graphics/Model/Polygon/Polygon2d.swift +++ b/ios/graphics/Model/Polygon/Polygon2d.swift @@ -142,23 +142,14 @@ extension Polygon2d: MCMaskingObjectInterface { extension Polygon2d: MCPolygon2dInterface { func setVertices(_ vertices: MCSharedBytes, indices: MCSharedBytes) { - guard let verticesBuffer = device.makeBuffer(from: vertices), - let indicesBuffer = device.makeBuffer(from: indices), - indices.elementCount > 0 - else { - lock.withCritical { - indicesCount = 0 - verticesBuffer = nil - indicesBuffer = nil - } - - return - } - lock.withCritical { - self.indicesCount = Int(indices.elementCount) - self.verticesBuffer = verticesBuffer - self.indicesBuffer = indicesBuffer + self.verticesBuffer.copyOrCreate(from: vertices, device: device) + self.indicesBuffer.copyOrCreate(from: indices, device: device) + if self.verticesBuffer != nil && self.indicesBuffer != nil { + self.indicesCount = Int(indices.elementCount) + } else { + self.indicesCount = 0 + } } } diff --git a/ios/graphics/Model/Polygon/PolygonGroup2d.swift b/ios/graphics/Model/Polygon/PolygonGroup2d.swift index ee125fdb0..8c336a6c5 100644 --- a/ios/graphics/Model/Polygon/PolygonGroup2d.swift +++ b/ios/graphics/Model/Polygon/PolygonGroup2d.swift @@ -111,28 +111,30 @@ extension PolygonGroup2d: MCPolygonGroup2dInterface { else { fatalError("Cannot allocate buffers for the UBTileModel") } + + var minX = Float.greatestFiniteMagnitude + var minY = Float.greatestFiniteMagnitude + + if shader.isStriped { + if let p = UnsafeRawPointer(bitPattern: Int(vertices.address)) { + for i in 0...stride * vertecies.count, options: []), let indicesBuffer = device.makeBuffer(bytes: indices, length: MemoryLayout.stride * indices.count, options: []) else { - fatalError("Cannot allocate buffers") - } - lock.withCritical { - indicesCount = indices.count - self.verticesBuffer = verticesBuffer - self.indicesBuffer = indicesBuffer + self.verticesBuffer.copyOrCreate(bytes: vertices, length: MemoryLayout.stride * vertices.count, device: device) + self.indicesBuffer.copyOrCreate(bytes: indices, length: MemoryLayout.stride * indices.count, device: device) + if self.verticesBuffer != nil && self.indicesBuffer != nil { + self.indicesCount = indices.count + } else { + self.indicesCount = 0 + } } } diff --git a/ios/maps/MCMapView.swift b/ios/maps/MCMapView.swift index 8559b65ff..b45599488 100644 --- a/ios/maps/MCMapView.swift +++ b/ios/maps/MCMapView.swift @@ -145,6 +145,7 @@ extension MCMapView: MTKViewDelegate { public func draw(in view: MTKView) { guard !backgroundDisable else { + isPaused = true return // don't execute metal calls in background } diff --git a/ios/maps/MCTextureLoader.swift b/ios/maps/MCTextureLoader.swift index 5b15fdcae..379489141 100644 --- a/ios/maps/MCTextureLoader.swift +++ b/ios/maps/MCTextureLoader.swift @@ -62,8 +62,12 @@ open class MCTextureLoader: MCLoaderInterface { open func loadTextureAsnyc(_ url: String, etag: String?) -> DJFuture { let urlString = url + let promise = DJPromise() + guard let url = URL(string: urlString) else { - preconditionFailure("invalid url: \(urlString)") + assertionFailure("invalid url: \(urlString)") + promise.setValue(.init(data: nil, etag: nil, status: .ERROR_NETWORK, errorCode: "IURL")) + return promise.getFuture() } var urlRequest = URLRequest(url: url) @@ -76,8 +80,6 @@ open class MCTextureLoader: MCLoaderInterface { wasCached = true } - let promise = DJPromise() - var task = session.dataTask(with: urlRequest) { [weak self] data, response_, error_ in guard let self else { return } @@ -211,16 +213,18 @@ open class MCTextureLoader: MCLoaderInterface { open func loadDataAsync(_ url: String, etag: String?) -> DJFuture { let urlString = url + let promise = DJPromise() + guard let url = URL(string: urlString) else { - preconditionFailure("invalid url: \(urlString)") + assertionFailure("invalid url: \(urlString)") + promise.setValue(.init(data: nil, etag: nil, status: .ERROR_NETWORK, errorCode: "IURL")) + return promise.getFuture() } var urlRequest = URLRequest(url: url) modifyUrlRequest(request: &urlRequest) - let promise = DJPromise() - var task = session.dataTask(with: urlRequest) { [weak self] data, response_, error_ in guard let self else { return } diff --git a/ios/readme.md b/ios/readme.md index 2698b54a5..df7b0ea52 100644 --- a/ios/readme.md +++ b/ios/readme.md @@ -31,7 +31,7 @@ For App integration within XCode, add this package to your App target. To do thi Once you have your Swift package set up, adding Open Mobile Maps as a dependency is as easy as adding it to the dependencies value of your Package.swift. ```swift dependencies: [ - .package(url: "https://github.com/openmobilemaps/maps-core.git", .upToNextMajor(from: "2.1.0")) + .package(url: "https://github.com/openmobilemaps/maps-core.git", .upToNextMajor(from: "2.2.0")) ] ``` diff --git a/shared/public/PerformanceLogger.cpp b/shared/public/PerformanceLogger.cpp new file mode 100644 index 000000000..5f0fd9f40 --- /dev/null +++ b/shared/public/PerformanceLogger.cpp @@ -0,0 +1,5 @@ +#include "PerformanceLogger.h" + +#ifdef ENABLE_PERF_LOGGING +thread_local PerformanceLogger::ThreadLocalData PerformanceLogger::local_data; +#endif diff --git a/shared/public/PerformanceLogger.h b/shared/public/PerformanceLogger.h new file mode 100644 index 000000000..e3bcca95d --- /dev/null +++ b/shared/public/PerformanceLogger.h @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "Logger.h" + +#pragma once + +// Define ENABLE_PERF_LOGGING to enable performance logging, comment out to disable it +//#define ENABLE_PERF_LOGGING + +#ifdef ENABLE_PERF_LOGGING + +#define PERF_LOG_START(key) PerformanceLogger::getInstance().startSection(key) +#define PERF_LOG_END(key) PerformanceLogger::getInstance().endSection(key) + +class PerformanceLogger { +public: + static PerformanceLogger& getInstance() { + static PerformanceLogger instance; + return instance; + } + + void startSection(const std::string& key) { + auto& start_time = local_data.start_times[key]; + start_time = std::chrono::high_resolution_clock::now(); + } + + void endSection(const std::string& key) { + auto end_time = std::chrono::high_resolution_clock::now(); + auto& start_time = local_data.start_times[key]; + auto duration = std::chrono::duration_cast(end_time - start_time).count(); + log(key, duration); + } + + void print_stats() { + std::lock_guard lock(global_map_mutex); + LogDebug <<= "--------------------------"; + LogDebug <<= "Key,Total Time,Unit,Average Time,Unit"; // CSV header + for (const auto& pair : global_map) { + double total = double(pair.second.first) / 1000.0; // Convert nanoseconds to milliseconds + const double count = pair.second.second; + std::string time_unit = "ms"; + const double average = count > 0 ? total / count : 0.0; + LogDebug << pair.first << "," << total << "," << time_unit << "," << average << "," <<= time_unit; + } + LogDebug <<= "--------------------------"; + } + + + +private: + struct ThreadLocalData { + std::unordered_map local_map; + std::unordered_map start_times; + std::unordered_map counts; + int estimated_global_size = 0; // Estimate of the global map size + }; + + static thread_local ThreadLocalData local_data; + std::unordered_map> global_map; + std::mutex global_map_mutex; + std::chrono::steady_clock::time_point last_print_time = std::chrono::steady_clock::now(); + + PerformanceLogger() {} + + void log(const std::string& key, long long duration) { + local_data.local_map[key] += duration; + local_data.counts[key]++; + + int flush_threshold = std::max(1, int(local_data.estimated_global_size * 0.1)); + if (++local_data.counts[key] % flush_threshold == 0) { + flush_local_data(); + bool print = false; + { + std::lock_guard lock(global_map_mutex); + auto now = std::chrono::steady_clock::now(); + print = std::chrono::duration_cast(now - last_print_time).count() > 10; + if (print) { + last_print_time = now; + } + } + if (print) { + print_stats(); + } + } + } + + void flush_local_data() { + std::lock_guard lock(global_map_mutex); + for (const auto& entry : local_data.local_map) { + global_map[entry.first].first += entry.second; + global_map[entry.first].second += local_data.counts[entry.first]; + } + // Update local estimated size after flushing + local_data.estimated_global_size = global_map.size(); + local_data.local_map.clear(); + local_data.counts.clear(); + } + +public: + PerformanceLogger(const PerformanceLogger&) = delete; + PerformanceLogger& operator=(const PerformanceLogger&) = delete; +}; + +#else + #define PERF_LOG_START(key) + #define PERF_LOG_END(key) +#endif diff --git a/shared/public/Tiled2dMapSourceImpl.h b/shared/public/Tiled2dMapSourceImpl.h index ab7c09e49..a50f3ca74 100644 --- a/shared/public/Tiled2dMapSourceImpl.h +++ b/shared/public/Tiled2dMapSourceImpl.h @@ -410,8 +410,16 @@ void Tiled2dMapSource::performLoadingTask(Tiled2dMapTileInfo tile, size [tile, loaderIndex, weakSelfPtr, weakActor, res] { auto strongSelf = weakSelfPtr.lock(); if (strongSelf) { - weakActor.message(&Tiled2dMapSource::didLoad, tile, loaderIndex, - strongSelf->postLoadingTask(res, tile)); + auto isStillVisible = weakActor.syncAccess([tile](auto actor){ + auto strongSelf = actor.lock(); + return strongSelf ? strongSelf->isTileVisible(tile) : false; + }); + if (isStillVisible == false) { + weakActor.message(&Tiled2dMapSource::didFailToLoad, tile, loaderIndex, LoaderStatus::ERROR_OTHER, std::nullopt); + } else { + weakActor.message(&Tiled2dMapSource::didLoad, tile, loaderIndex, + strongSelf->postLoadingTask(res, tile)); + } } })); } diff --git a/shared/public/Tiled2dMapVectorLayerConfig.h b/shared/public/Tiled2dMapVectorLayerConfig.h index 22ba82162..8c433e4b4 100644 --- a/shared/public/Tiled2dMapVectorLayerConfig.h +++ b/shared/public/Tiled2dMapVectorLayerConfig.h @@ -32,6 +32,29 @@ class Tiled2dMapVectorLayerConfig : public Tiled2dMapLayerConfig { std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override { std::string url = sourceDescription->vectorUrl; + size_t epsg3857Index = url.find("{bbox-epsg-3857}", 0); + if (epsg3857Index != std::string::npos) { + const auto zoomLevelInfos = getDefaultEpsg3857ZoomLevels(zoom, zoom); + const Tiled2dMapZoomLevelInfo &zoomLevelInfo = zoomLevelInfos.at(0); + RectCoord layerBounds = zoomLevelInfo.bounds; + const double tileWidth = zoomLevelInfo.tileWidthLayerSystemUnits; + + const bool leftToRight = layerBounds.topLeft.x < layerBounds.bottomRight.x; + const bool topToBottom = layerBounds.topLeft.y < layerBounds.bottomRight.y; + const double tileWidthAdj = leftToRight ? tileWidth : -tileWidth; + const double tileHeightAdj = topToBottom ? tileWidth : -tileWidth; + + const double boundsLeft = layerBounds.topLeft.x; + const double boundsTop = layerBounds.topLeft.y; + + const Coord topLeft = Coord(epsg3857Id, x * tileWidthAdj + boundsLeft, y * tileHeightAdj + boundsTop, 0); + const Coord bottomRight = Coord(epsg3857Id, topLeft.x + tileWidthAdj, topLeft.y + tileHeightAdj, 0); + + std::string boxString = std::to_string(topLeft.x) + "," + std::to_string(bottomRight.y) + "," + std::to_string(bottomRight.x) + "," + std::to_string(topLeft.y); + url = url.replace(epsg3857Index, 16, boxString); + return url; + + } size_t zoomIndex = url.find("{z}", 0); if (zoomIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); url = url.replace(zoomIndex, 3, std::to_string(zoom)); diff --git a/shared/public/Tiled2dMapVectorStyleParser.h b/shared/public/Tiled2dMapVectorStyleParser.h index da16f4444..3bf46a6cb 100644 --- a/shared/public/Tiled2dMapVectorStyleParser.h +++ b/shared/public/Tiled2dMapVectorStyleParser.h @@ -361,6 +361,10 @@ class Tiled2dMapVectorStyleParser { std::vector>> steps; for (auto const stop: json[stopsExpression]) { + if (!stop[0].is_number()) { + LogError <<= "Tiled2dMapVectorStyleParser not handled: " + json.dump(); + return nullptr; + } steps.push_back({stop[0].get(), parseValue(stop[1])}); } diff --git a/shared/public/Value.h b/shared/public/Value.h index 67cb3ce50..5b9f10112 100644 --- a/shared/public/Value.h +++ b/shared/public/Value.h @@ -1087,14 +1087,6 @@ class InterpolatedValue : public Value { ValueVariant interpolate(const double &interpolationFactor, const ValueVariant &yBase, const ValueVariant &yTop) const { - if (std::holds_alternative(yBase) && std::holds_alternative(yTop)) { - if (interpolationFactor < 0.5 ){ - return yBase; - } else { - return yTop; - } - } - if (std::holds_alternative(yBase) && std::holds_alternative(yTop)) { return std::get(yBase) + (std::get(yTop) - std::get(yBase)) * interpolationFactor; } @@ -1131,8 +1123,12 @@ class InterpolatedValue : public Value { yBaseC.b + (yTopC.b - yBaseC.b) * interpolationFactor, yBaseC.a + (yTopC.a - yBaseC.a) * interpolationFactor); } - assert(false); - return 0; + + if (interpolationFactor < 0.5 ){ + return yBase; + } else { + return yTop; + } } private: diff --git a/shared/src/MapsCoreSharedModule.cpp b/shared/src/MapsCoreSharedModule.cpp index 0fd567991..7663f40b4 100644 --- a/shared/src/MapsCoreSharedModule.cpp +++ b/shared/src/MapsCoreSharedModule.cpp @@ -10,4 +10,4 @@ #include "MapsCoreSharedModule.h" -std::string MapsCoreSharedModule::version() { return "2.1.0"; } +std::string MapsCoreSharedModule::version() { return "2.2.0"; } diff --git a/shared/src/map/camera/MapCamera2d.cpp b/shared/src/map/camera/MapCamera2d.cpp index 7b43397f6..95532c1c0 100644 --- a/shared/src/map/camera/MapCamera2d.cpp +++ b/shared/src/map/camera/MapCamera2d.cpp @@ -95,7 +95,17 @@ void MapCamera2d::moveToCenterPositionZoom(const ::Coord ¢erPosition, double this->coordAnimation = nullptr; }); coordAnimation->start(); - setZoom(adjustedZoom, true); + double targetZoom = std::clamp(zoom, zoomMax, zoomMin); + zoomAnimation = std::make_shared( + DEFAULT_ANIM_LENGTH, this->zoom, targetZoom, InterpolatorFunction::EaseIn, + [=](double zoom) { + this->zoom = zoom; + }, + [=] { + this->zoom = targetZoom; + this->zoomAnimation = nullptr; + }); + zoomAnimation->start(); mapInterface->invalidate(); } else { this->centerPosition = targetPosition; diff --git a/shared/src/map/layers/polygon/PolygonLayer.cpp b/shared/src/map/layers/polygon/PolygonLayer.cpp index 5e91a7514..955451168 100644 --- a/shared/src/map/layers/polygon/PolygonLayer.cpp +++ b/shared/src/map/layers/polygon/PolygonLayer.cpp @@ -330,6 +330,11 @@ void PolygonLayer::resetSelection() { } bool PolygonLayer::onTouchDown(const ::Vec2F &posScreen) { + const auto handler = callbackHandler; + if (!handler) { + return false; + } + auto point = mapInterface->getCamera()->coordFromScreenPosition(posScreen); std::lock_guard lock(polygonsMutex); @@ -376,12 +381,14 @@ void PolygonLayer::clearTouch() { } bool PolygonLayer::onClickUnconfirmed(const ::Vec2F &posScreen) { + const auto handler = callbackHandler; + if (!handler) { + return false; + } if (highlightedPolygon) { selectedPolygon = highlightedPolygon; - if (callbackHandler) { - callbackHandler->onClickConfirmed(*selectedPolygon); - } + handler->onClickConfirmed(*selectedPolygon); highlightedPolygon = std::nullopt; mapInterface->invalidate(); diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp index 1d716e263..62ceb9b9c 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp @@ -354,7 +354,8 @@ void Tiled2dMapVectorLayer::initializeVectorLayer() { auto sourceMailbox = std::make_shared(mapInterface->getScheduler()); Actor vectorSource; - if (mapDescription->geoJsonSources.count(source) != 0) { + auto geoJsonSourceIt = mapDescription->geoJsonSources.find(source); + if (geoJsonSourceIt != mapDescription->geoJsonSources.end()) { auto geoJsonSource = Actor(sourceMailbox, mapInterface->getCamera(), mapInterface->getMapConfig(), @@ -370,7 +371,7 @@ void Tiled2dMapVectorLayer::initializeVectorLayer() { layerName); vectorSource = geoJsonSource.strongActor(); - mapDescription->geoJsonSources.at(source)->setDelegate(geoJsonSource.weakActor()); + geoJsonSourceIt->second->setDelegate(geoJsonSource.weakActor()); } else { vectorSource = Actor(sourceMailbox, @@ -518,10 +519,12 @@ void Tiled2dMapVectorLayer::reloadDataSource(const std::string &sourceName) { } void Tiled2dMapVectorLayer::reloadLocalDataSource(const std::string &sourceName, const std::string &geoJson) { - std::lock_guard lock(mapDescriptionMutex); - if (!mapInterface) { + auto mapInterface = this->mapInterface; + auto mapDescription = this->mapDescription; + + if (!mapInterface || !mapDescription) { return; } diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorSource.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorSource.cpp index 50a9c200d..c67332c16 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorSource.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorSource.cpp @@ -13,6 +13,7 @@ #include "vtzero/vector_tile.hpp" #include "Logger.h" #include "Tiled2dMapVectorTileInfo.h" +#include "PerformanceLogger.h" Tiled2dMapVectorSource::Tiled2dMapVectorSource(const MapConfig &mapConfig, const std::shared_ptr &layerConfig, @@ -46,6 +47,7 @@ bool Tiled2dMapVectorSource::hasExpensivePostLoadingTask() { } Tiled2dMapVectorTileInfo::FeatureMap Tiled2dMapVectorSource::postLoadingTask(std::shared_ptr loadedData, Tiled2dMapTileInfo tile) { + PERF_LOG_START(sourceName + "_postLoadingTask"); auto layerFeatureMap = std::make_shared>>>(); if (!loadedData->data.has_value()) { @@ -65,6 +67,7 @@ Tiled2dMapVectorTileInfo::FeatureMap Tiled2dMapVectorSource::postLoadingTask(std layerFeatureMap->at(sourceLayerName)->reserve(layer.num_features()); while (const auto &feature = layer.next_feature()) { auto const featureContext = std::make_shared(feature); + PERF_LOG_START(sourceLayerName + "_decode"); try { std::shared_ptr geometryHandler = std::make_shared(tile.bounds, extent, layerConfig->getVectorSettings(), conversionHelper); vtzero::decode_geometry(feature.geometry(), *geometryHandler); @@ -74,6 +77,7 @@ Tiled2dMapVectorTileInfo::FeatureMap Tiled2dMapVectorSource::postLoadingTask(std LogError <<= "geometryException for tile " + std::to_string(tile.zoomIdentifier) + "/" + std::to_string(tile.x) + "/" + std::to_string(tile.y); continue; } + PERF_LOG_END(sourceLayerName + "_decode"); } if (layerFeatureMap->at(sourceLayerName)->empty()) { layerFeatureMap->erase(sourceLayerName); @@ -89,7 +93,7 @@ Tiled2dMapVectorTileInfo::FeatureMap Tiled2dMapVectorSource::postLoadingTask(std LogError <<= "Unknown wire type exception for tile " + std::to_string(tile.zoomIdentifier) + "/" + std::to_string(tile.x) + "/" + std::to_string(tile.y); } - + PERF_LOG_START(sourceName + "_postLoadingTask"); return layerFeatureMap; } diff --git a/shared/src/map/layers/tiled/vector/geojson/GeoJsonParser.h b/shared/src/map/layers/tiled/vector/geojson/GeoJsonParser.h index fc7ff87ef..6e6d74825 100644 --- a/shared/src/map/layers/tiled/vector/geojson/GeoJsonParser.h +++ b/shared/src/map/layers/tiled/vector/geojson/GeoJsonParser.h @@ -46,7 +46,8 @@ class GeoJsonParser { std::shared_ptr geoJson = std::make_shared(); for (const auto &feature: geojson["features"]) { - if (!feature["geometry"].is_object() || + if (!feature.contains("geometry") || + !feature["geometry"].is_object() || !feature["geometry"]["type"].is_string() || !feature["geometry"]["coordinates"].is_array()) { LogError <<= "Geojson feature is not valid"; diff --git a/shared/src/map/layers/tiled/vector/geojson/geojsonvt/geojsonvt.hpp b/shared/src/map/layers/tiled/vector/geojson/geojsonvt/geojsonvt.hpp index a655c402a..b7d661dc2 100644 --- a/shared/src/map/layers/tiled/vector/geojson/geojsonvt/geojsonvt.hpp +++ b/shared/src/map/layers/tiled/vector/geojson/geojsonvt/geojsonvt.hpp @@ -52,19 +52,7 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this< GeoJSONVT(const std::shared_ptr &geoJson, const Options& options_ = Options()) : options(options_), loadingResult(DataLoaderResult(std::nullopt, std::nullopt, LoaderStatus::OK, std::nullopt)) { - - // If the GeoJSON contains only points, there is no need to split it into smaller tiles, - // as there are no opportunities for simplification, merging, or meaningful point reduction. - if (geoJson->hasOnlyPoints) { - options.maxZoom = options.minZoom; - } - - - const uint32_t z2 = 1u << options.maxZoom; - - convert(geoJson->geometries, (options.tolerance / options.extent) / z2); - - splitTile(geoJson->geometries, 0, 0, 0); + initialize(geoJson); } GeoJSONVT(const std::string &sourceName, @@ -117,17 +105,7 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this< auto geoJson = GeoJsonParser::getGeoJson(json); if (geoJson) { - // If the GeoJSON contains only points, there is no need to split it into smaller tiles, - // as there are no opportunities for simplification, merging, or meaningful point reduction. - if (geoJson->hasOnlyPoints) { - self->options.maxZoom = self->options.minZoom; - } - - const uint32_t z2 = 1u << self->options.maxZoom; - - convert(geoJson->geometries, (self->options.tolerance / self->options.extent) / z2); - - self->splitTile(geoJson->geometries, 0, 0, 0); + self->initialize(geoJson); self->delegate.message(&GeoJSONTileDelegate::didLoad, self->options.maxZoom); } @@ -147,6 +125,20 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this< }); } + void initialize(const std::shared_ptr &geoJson) { + // If the GeoJSON contains only points, there is no need to split it into smaller tiles, + // as there are no opportunities for simplification, merging, or meaningful point reduction. + if (geoJson->hasOnlyPoints) { + options.maxZoom = options.minZoom; + } + + const uint32_t z2 = 1u << options.maxZoom; + + convert(geoJson->geometries, (options.tolerance / options.extent) / z2); + + splitTile(geoJson->geometries, 0, 0, 0); + } + void setDelegate(const WeakActor delegate) override { this->delegate = delegate; if (loadingResult) { diff --git a/shared/src/map/layers/tiled/vector/geojson/geojsonvt/tile.hpp b/shared/src/map/layers/tiled/vector/geojson/geojsonvt/tile.hpp index f313bcf6c..1f09e6bbf 100644 --- a/shared/src/map/layers/tiled/vector/geojson/geojsonvt/tile.hpp +++ b/shared/src/map/layers/tiled/vector/geojson/geojsonvt/tile.hpp @@ -102,7 +102,7 @@ class InternalTile { temp.reserve(geometry->holes.at(index).size()); for (auto &points : geometry->holes.at(index)) { std::vector<::Coord> temp1; - simplify(temp1, tolerance); + simplify(points, tolerance); temp1.reserve(points.size()); for (const auto &point : points) { if (point.z > sq_tolerance) { diff --git a/shared/src/map/layers/tiled/vector/sourcemanagers/Tiled2dMapVectorSourceTileDataManager.cpp b/shared/src/map/layers/tiled/vector/sourcemanagers/Tiled2dMapVectorSourceTileDataManager.cpp index 5838fd39a..59f06d21f 100644 --- a/shared/src/map/layers/tiled/vector/sourcemanagers/Tiled2dMapVectorSourceTileDataManager.cpp +++ b/shared/src/map/layers/tiled/vector/sourcemanagers/Tiled2dMapVectorSourceTileDataManager.cpp @@ -15,6 +15,7 @@ #include "Tiled2dMapVectorLineTile.h" #include "Tiled2dMapVectorLayer.h" #include "RenderPass.h" +#include "PerformanceLogger.h" void Tiled2dMapVectorSourceTileDataManager::update() { if (!noPendingUpdateMasks.test_and_set()) { @@ -35,9 +36,11 @@ void Tiled2dMapVectorSourceTileDataManager::update() { } for (const auto &[index, identifier, tile]: subTiles) { + PERF_LOG_START(identifier + "_update"); tile.syncAccess([](auto t) { t->update(); }); + PERF_LOG_END(identifier + "_update"); } } }