Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
b09236d
add auto-translation script using OpenAI API
wf9a5m75 Dec 1, 2025
8c46f83
commonalize code blocks
wf9a5m75 Dec 3, 2025
015e238
Componentize all code blocks across all JA documentations
wf9a5m75 Dec 3, 2025
a9d4285
prevent crashes
wf9a5m75 Dec 4, 2025
5a8618b
wip: fixing the documents
wf9a5m75 Dec 4, 2025
bd3e44f
wip: fixing the documents
wf9a5m75 Dec 4, 2025
ee4bcf1
wip: fixing the documents
wf9a5m75 Dec 4, 2025
db75b31
wip: fixing the documents
wf9a5m75 Dec 4, 2025
3701da8
Merge branch 'main' into v1.1.3
wf9a5m75 Dec 4, 2025
3f10c47
wip: fixing the documents
wf9a5m75 Dec 4, 2025
de534a5
wip: fixing the documents
wf9a5m75 Dec 5, 2025
c0fbd48
wip: fixing the documents
wf9a5m75 Dec 5, 2025
0481347
wip: fixing the documents
wf9a5m75 Dec 5, 2025
ee760a0
wip: fixing the documents
wf9a5m75 Dec 5, 2025
233c046
Implement the sdkInitialize callback property for ArcGISMapView
wf9a5m75 Dec 10, 2025
5baa673
sample: sdkInitialize for ArcGISMapView
wf9a5m75 Dec 10, 2025
2aefe78
update: ArcGISMapViewHoler pages
wf9a5m75 Dec 10, 2025
1748042
import ArcGISOAuthHybridInitialize
wf9a5m75 Dec 11, 2025
bbd8519
fix: build error of the example-app
wf9a5m75 Dec 13, 2025
ef9b556
add the marker animation sections
wf9a5m75 Dec 15, 2025
efa1825
creating event page
wf9a5m75 Dec 16, 2025
39c26b1
fix: altitudeToZoomLevel is incorrect
wf9a5m75 Dec 19, 2025
fbdcc26
remove okhttp dependencies
wf9a5m75 Dec 19, 2025
5ce1eb0
remove event/onMapLoaded
wf9a5m75 Dec 19, 2025
3a5fbc1
wip
wf9a5m75 Dec 26, 2025
09fec07
fix: GeoPoint
wf9a5m75 Dec 27, 2025
46eec0d
fix: Align the class name with others: "MapLibreDesignType"
wf9a5m75 Dec 29, 2025
69408b1
add: some predefined map designs
wf9a5m75 Dec 29, 2025
997b5c6
feat: Polyline and Polygon components accepts GeoRectBounds
wf9a5m75 Dec 29, 2025
2b93a9b
fix: Align the class name with others: "MapLibreDesignType"
wf9a5m75 Dec 29, 2025
596820b
fix: GeoRectBounds.union generates a new bounds
wf9a5m75 Dec 29, 2025
88a89a5
add: some predefined map designs
wf9a5m75 Dec 29, 2025
fc9371b
update UnionBoundsExample
wf9a5m75 Dec 30, 2025
f3eac73
fix: new map tile designs for MapLibre cause a crash
wf9a5m75 Dec 30, 2025
859d5d2
fix: the title in the sidebar becomes under the camera position
wf9a5m75 Dec 30, 2025
6498274
refactor: move overlay event callbacks to state objects
wf9a5m75 Dec 31, 2025
c2d268d
feat: MapCameraPosition accepts GeoPoint object for the position para…
wf9a5m75 Dec 31, 2025
5f644f2
fix: changing marker.position does not work
wf9a5m75 Dec 31, 2025
02fd812
update examples
wf9a5m75 Dec 31, 2025
c90a24c
fix: linter errors
wf9a5m75 Dec 31, 2025
0c4064a
feat: MarkerRenderingStrategy is renamed to MarkerRenderingGroup, and…
wf9a5m75 Dec 31, 2025
3d152eb
Feature: Introducing MarkerClusterGroup (#86)
wf9a5m75 Jan 1, 2026
3431348
Add RasterLayer, TileServer-based HeatmapOverlay across providers (#87)
wf9a5m75 Jan 2, 2026
48aaaa5
add ARCHITECTURE.md
wf9a5m75 Jan 2, 2026
0bb25b8
fix: typo "durationMills" to "durationMillis"
wf9a5m75 Jan 2, 2026
81f48db
fix: onGroundImageClick is not used
wf9a5m75 Jan 2, 2026
fa2fb8a
rename: DefaultIcon is renamed to DefaultMarkerIcon
wf9a5m75 Jan 3, 2026
61f482a
refact: xxxImpl(実装) と xxx(インタフェース)を、xxx(実装)と xxxInterface(インタフェース)に変更
wf9a5m75 Jan 4, 2026
a5422de
split the core module into two modules: "core(jetpack runtime)" and "…
wf9a5m75 Jan 7, 2026
21f66ed
fix: ktLint errors
wf9a5m75 Jan 7, 2026
0d4ae39
Get rid of the MarkerRenderingGroup and MarkerClusterGroup from each …
wf9a5m75 Jan 8, 2026
ac4d02e
Add marker clustering demo
wf9a5m75 Jan 8, 2026
d297a59
refactor: some spherical functions were moved into each separate file
wf9a5m75 Jan 8, 2026
9c65969
feat: implement enableZoomAnimation for MarkerClusterGroup
wf9a5m75 Jan 8, 2026
66be543
remove old cluster marker after marker animation is done
wf9a5m75 Jan 8, 2026
d1428c2
remove old cluster marker after marker animation is done
wf9a5m75 Jan 8, 2026
fe4c3b0
optimize marker clustering performance on panning the map
wf9a5m75 Jan 8, 2026
1aabe83
optimize marker animation for marker clustering
wf9a5m75 Jan 9, 2026
e457aa1
optimize marker animation for marker clustering
wf9a5m75 Jan 9, 2026
151c61d
Provide TileServer feature by default instead of isolated module
wf9a5m75 Jan 9, 2026
5714d63
Merge branch 'v1.1.3' of github.com:MapConductor/android-sdk into v1.1.3
wf9a5m75 Jan 9, 2026
f5555e8
Put back the mapconductor-core-domain into the mapconductor-core module.
wf9a5m75 Jan 9, 2026
e2b27ea
rename "bitmap" to "image"
wf9a5m75 Jan 10, 2026
098ca2f
remove unused code
wf9a5m75 Jan 10, 2026
7623f2a
fix: Can not load OSM raster tile
wf9a5m75 Jan 10, 2026
8ef98ab
fix: Can not load OSM raster tile
wf9a5m75 Jan 10, 2026
b83b90d
introduce ChildCollector
wf9a5m75 Jan 11, 2026
53b9725
fix: heatmap tile drawing issue
wf9a5m75 Jan 11, 2026
176b9d6
implement GroundImage for MapLibre
wf9a5m75 Jan 11, 2026
732a839
implement GroundImage for Mapbox
wf9a5m75 Jan 11, 2026
4a59a3e
wip: heatmap optimization
wf9a5m75 Jan 11, 2026
4f07ff8
wip: heatmap optimization
wf9a5m75 Jan 11, 2026
65f430c
ignore .gradle-user-home
wf9a5m75 Jan 11, 2026
8b609be
implement GroundImage for Here
wf9a5m75 Jan 11, 2026
2fe6485
Merge branch 'v1.1.3' of github.com:MapConductor/android-sdk into v1.1.3
wf9a5m75 Jan 11, 2026
a6a8f46
feat: implement GroundImage for ArcGIS
wf9a5m75 Jan 11, 2026
41256fa
V1.1.3 sync ios (#88)
wf9a5m75 Jan 22, 2026
65ea10f
fix: marker tiling ignore the anchor and the scale properties
wf9a5m75 Jan 22, 2026
746e8c7
fix: build errors
wf9a5m75 Jan 22, 2026
837c4ac
introduce MarkerTileRendering
wf9a5m75 Jan 25, 2026
246be56
fix: ArcGIS camera bearing calculation is incorrect
wf9a5m75 Feb 5, 2026
f89a741
No longer necessary homography
wf9a5m75 Feb 5, 2026
42a6dd7
feat: introduce the hole property for Polygon
wf9a5m75 Feb 5, 2026
b5716ef
feat: introduce the hole property for Polygon
wf9a5m75 Feb 5, 2026
8a12281
feat: introduce the hole property for Polygon
wf9a5m75 Feb 5, 2026
22ae892
feat: introduce the hole property for Polygon
wf9a5m75 Feb 5, 2026
42b8a29
refact: align the zoom levels
wf9a5m75 Feb 5, 2026
3337932
feat: add zIndex property to map overlays and improve camera callbacks
wf9a5m75 Feb 5, 2026
8580da2
Remove unnecessary file
wf9a5m75 Feb 5, 2026
7d27856
fix: Marker click detection does not work when markerState.icon is null
wf9a5m75 Feb 5, 2026
1b83529
code cleanup
wf9a5m75 Feb 7, 2026
2da3574
Adjust the ArcGIZ Optimized zoom0 value
wf9a5m75 Feb 22, 2026
237bd7a
Adjusting the ArcGIZ zoom levels
wf9a5m75 Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"Bash(cat:*)",
"Read(//Users/masashi/.gradle/caches/**)",
"Bash(xargs jar tf:*)",
"Bash(npm run build:*)"
"Bash(npm run build:*)",
"WebFetch(domain:medium.com)",
"WebSearch"
],
"deny": [],
"ask": []
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ compose-map-story/
slides/
*.secrets
act.env
.gradle-user-home
arcgis-heatmap-issue.txt
session.txt
103 changes: 0 additions & 103 deletions CLAUDE.md

This file was deleted.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ A unified mapping library that provides a common API for multiple map providers

### Key Components

- **MapViewController**: Abstract controller interface for all map providers
- **MapViewControllerInterface**: Abstract controller interface for all map providers
- **MapViewBase**: Generic Compose-based map view component
- **Overlay Managers**: Separate managers for markers, circles, polylines, and polygons
- **Projection Utilities**: WebMercator and WGS84 coordinate transformations
- **HexGeocell**: Spatial indexing system for performance optimization
- **ProjectionInterface Utilities**: WebMercator and WGS84 coordinate transformations
- **HexGeocellInterface**: Spatial indexing system for performance optimization

## Quick Start

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

public struct GoogleMapMarkerTilingOptions: Equatable, Hashable, Sendable {
public var enabled: Bool
public var minMarkerCount: Int

public init(
enabled: Bool = true,
minMarkerCount: Int = 2000
) {
self.enabled = enabled
self.minMarkerCount = minMarkerCount
}

public static let disabled: GoogleMapMarkerTilingOptions = .init(enabled: false)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import CoreGraphics
import GoogleMaps
import UIKit

internal final class GoogleMapTiledMarkerLayer: GMSTileLayer {
internal struct RenderMarker {
let id: String
let latitude: Double
let longitude: Double
let visible: Bool
let image: CGImage
let anchorX: CGFloat
let anchorY: CGFloat
let drawWidth: Double
let drawHeight: Double
}

private let tileSizePx: Int
private let renderScale: Int
private let lock = NSLock()

private var markersById: [String: RenderMarker] = [:]
private var indexedZoom: Int = -1
private var tileToMarkerIds: [UInt64: [String]] = [:]

init(
mapView: GMSMapView,
tileSizePx: Int = 256,
zIndex: Int32 = 0
) {
self.tileSizePx = max(1, tileSizePx)
self.renderScale = min(2, max(1, Int(round(UIScreen.main.scale))))
super.init()
tileSize = self.tileSizePx
self.zIndex = zIndex
map = mapView
}

func setMarkers(
_ markers: [String: RenderMarker],
zoom: Int
) {
lock.lock()
markersById = markers
rebuildIndexLocked(zoom: zoom)
lock.unlock()
clearTileCache()
}

func setZoom(_ zoom: Int) {
lock.lock()
if zoom == indexedZoom {
lock.unlock()
return
}
rebuildIndexLocked(zoom: zoom)
lock.unlock()
clearTileCache()
}

func remove() {
map = nil
}

override func tileFor(x: UInt, y: UInt, zoom: UInt) -> UIImage? {
let requestedZoom = Int(zoom)
let worldTileCount = 1 << requestedZoom
if Int(y) < 0 || Int(y) >= worldTileCount { return nil }

let normalizedX = normalizeTileX(Int(x), worldTileCount: worldTileCount)
let key = tileKey(x: normalizedX, y: Int(y))

lock.lock()
let zoomIndex = (requestedZoom == indexedZoom) ? tileToMarkerIds : [:]
let ids = zoomIndex[key] ?? []
let markers = markersById
lock.unlock()

if ids.isEmpty { return nil }

let renderTileSize = tileSizePx * renderScale
let format = UIGraphicsImageRendererFormat.default()
format.scale = 1
format.opaque = false

let renderImage = UIGraphicsImageRenderer(size: CGSize(width: renderTileSize, height: renderTileSize), format: format).image { ctx in
let context = ctx.cgContext
context.setAllowsAntialiasing(true)
context.setShouldAntialias(true)
context.interpolationQuality = .high
if renderScale != 1 {
context.scaleBy(x: CGFloat(renderScale), y: CGFloat(renderScale))
}

let worldPixelSize = Double(worldTileCount * tileSizePx)
let tileOriginX = Double(normalizedX * tileSizePx)
let tileOriginY = Double(Int(y) * tileSizePx)

for id in ids {
guard let marker = markers[id] else { continue }
if !marker.visible { continue }
let pixel = mercatorPixel(latitude: marker.latitude, longitude: marker.longitude, worldPixelSize: worldPixelSize)
let localX = pixel.x - tileOriginX
let localY = pixel.y - tileOriginY

let left = Double(localX) - Double(marker.anchorX) * marker.drawWidth
let top = Double(localY) - Double(marker.anchorY) * marker.drawHeight
let rect = CGRect(x: left, y: top, width: marker.drawWidth, height: marker.drawHeight)
context.draw(marker.image, in: rect)
}
}

if renderScale == 1 { return renderImage }

let finalImage = UIGraphicsImageRenderer(size: CGSize(width: tileSizePx, height: tileSizePx), format: format).image { ctx in
ctx.cgContext.interpolationQuality = .high
renderImage.draw(in: CGRect(x: 0, y: 0, width: tileSizePx, height: tileSizePx))
}
return finalImage
}

private struct Pixel {
let x: Double
let y: Double
}

private func mercatorPixel(
latitude: Double,
longitude: Double,
worldPixelSize: Double
) -> Pixel {
let clampedLatitude = min(85.05112878, max(-85.05112878, latitude))
let sinLatitude = min(0.9999, max(-0.9999, sin(clampedLatitude * .pi / 180.0)))
let x = (longitude + 180.0) / 360.0
let y = 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * .pi)

let pixelX = normalizePixel(x * worldPixelSize, worldPixelSize: worldPixelSize)
let pixelY = min(worldPixelSize - 1.0, max(0.0, y * worldPixelSize))
return Pixel(x: pixelX, y: pixelY)
}

private func normalizePixel(_ pixel: Double, worldPixelSize: Double) -> Double {
let wrapped = pixel.truncatingRemainder(dividingBy: worldPixelSize)
return wrapped < 0 ? wrapped + worldPixelSize : wrapped
}

private func normalizeTileX(_ x: Int, worldTileCount: Int) -> Int {
let wrapped = x % worldTileCount
return wrapped < 0 ? wrapped + worldTileCount : wrapped
}

private func tileKey(x: Int, y: Int) -> UInt64 {
(UInt64(bitPattern: Int64(x)) << 32) ^ (UInt64(bitPattern: Int64(y)) & 0xffffffff)
}

private func rebuildIndexLocked(zoom: Int) {
if markersById.isEmpty {
indexedZoom = zoom
tileToMarkerIds = [:]
return
}

let worldTileCount = 1 << zoom
let worldPixelSize = Double(worldTileCount * tileSizePx)
var tiles: [UInt64: [String]] = [:]
tiles.reserveCapacity(min(4096, markersById.count))

for marker in markersById.values {
if !marker.visible { continue }
let pixel = mercatorPixel(latitude: marker.latitude, longitude: marker.longitude, worldPixelSize: worldPixelSize)
let left = pixel.x - Double(marker.anchorX) * marker.drawWidth
let top = pixel.y - Double(marker.anchorY) * marker.drawHeight
let right = left + marker.drawWidth
let bottom = top + marker.drawHeight

let minTileX = Int(floor(left / Double(tileSizePx)))
let maxTileX = Int(floor((right - 1.0) / Double(tileSizePx)))
let minTileY = Int(floor(top / Double(tileSizePx)))
let maxTileY = Int(floor((bottom - 1.0) / Double(tileSizePx)))

if minTileY >= worldTileCount || maxTileY < 0 { continue }
for tileY in minTileY...maxTileY {
if tileY < 0 || tileY >= worldTileCount { continue }
for tileX in minTileX...maxTileX {
let normalizedX = normalizeTileX(tileX, worldTileCount: worldTileCount)
let key = tileKey(x: normalizedX, y: tileY)
if tiles[key] == nil { tiles[key] = [] }
tiles[key]?.append(marker.id)
}
}
}

indexedZoom = zoom
tileToMarkerIds = tiles
}
}

6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
import java.util.Properties
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.jlleitschuh.ktlint) apply false
id("com.gradleup.nmcp") version "0.0.8"
Expand Down Expand Up @@ -60,7 +61,7 @@ tasks.register("allLintChecks") {
val lintTasks =
modules
.filter { it != "mapconductor-bom" }
.map { module ->
.flatMap { module ->
listOf(":$module:ktlintFormat", ":$module:lint")
}

Expand Down Expand Up @@ -121,3 +122,4 @@ nmcp {
// All publications from all subprojects will be published
}
}

Loading