This commit is contained in:
Jacob Powers 2025-07-18 01:28:21 +00:00
parent 220a062114
commit 6adefdb3aa
12 changed files with 433 additions and 154 deletions

View file

@ -5848,70 +5848,6 @@
}
}
},
"Map Overlays" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Karten-Overlays"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Superpositions de cartes"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "שכבות מפה"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sovrapposizioni mappa"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "マップオーバーレイ"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nakładki map"
}
},
"se" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kartöverlägg"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Преклапања мапе"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "地图覆盖层"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "地圖覆蓋層"
}
}
}
},
"Button GPIO" : {
"localizations" : {
"it" : {
@ -20566,6 +20502,70 @@
}
}
},
"Map Overlays" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Karten-Overlays"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Superpositions de cartes"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "שכבות מפה"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sovrapposizioni mappa"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "マップオーバーレイ"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nakładki map"
}
},
"se" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kartöverlägg"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Преклапања мапе"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "地图覆盖层"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "地圖覆蓋層"
}
}
}
},
"Map Publish Interval" : {
"localizations" : {
"it" : {
@ -37980,9 +37980,6 @@
}
}
}
},
"Toilets" : {
},
"Topic: %@" : {
"localizations" : {

View file

@ -57,9 +57,10 @@
25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5C12C3F6E4B008036E3 /* AppState.swift */; };
25F5D5D12C4375DF008036E3 /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F5D5D02C4375DF008036E3 /* RouterTests.swift */; };
3D3417B42E2730EC006A988B /* GeoJSONOverlayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417B32E2730EC006A988B /* GeoJSONOverlayManager.swift */; };
3D3417BC2E273AC6006A988B /* Street_Outlines.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 3D3417BB2E273AC6006A988B /* Street_Outlines.geojson */; };
3D3417C32E274800006A988B /* Toilets.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 3D3417C12E274800006A988B /* Toilets.geojson */; };
3D3417C42E274800006A988B /* Trash_Fence.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 3D3417C22E274800006A988B /* Trash_Fence.geojson */; };
3D3417C82E29D38A006A988B /* GeoJSONOverlayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */; };
3D3417CB2E29D3B0006A988B /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417C92E29D3B0006A988B /* Color+Hex.swift */; };
3D3417CC2E29D3B0006A988B /* Data+Gzip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417CA2E29D3B0006A988B /* Data+Gzip.swift */; };
3D3417CE2E29D5D6006A988B /* BurningManGeoJSONMapConfig.json.zlib in Resources */ = {isa = PBXBuildFile; fileRef = 3D3417CD2E29D5D6006A988B /* BurningManGeoJSONMapConfig.json.zlib */; };
6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D825E612C34786C008DBEE4 /* CommonRegex.swift */; };
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; };
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; };
@ -331,9 +332,10 @@
25F5D5C72C4375A8008036E3 /* MeshtasticTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeshtasticTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
25F5D5D02C4375DF008036E3 /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = "<group>"; };
3D3417B32E2730EC006A988B /* GeoJSONOverlayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJSONOverlayManager.swift; sourceTree = "<group>"; };
3D3417BB2E273AC6006A988B /* Street_Outlines.geojson */ = {isa = PBXFileReference; lastKnownFileType = text; path = Street_Outlines.geojson; sourceTree = "<group>"; };
3D3417C12E274800006A988B /* Toilets.geojson */ = {isa = PBXFileReference; lastKnownFileType = text; path = Toilets.geojson; sourceTree = "<group>"; };
3D3417C22E274800006A988B /* Trash_Fence.geojson */ = {isa = PBXFileReference; lastKnownFileType = text; path = Trash_Fence.geojson; sourceTree = "<group>"; };
3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJSONOverlayConfig.swift; sourceTree = "<group>"; };
3D3417C92E29D3B0006A988B /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = "<group>"; };
3D3417CA2E29D3B0006A988B /* Data+Gzip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Gzip.swift"; sourceTree = "<group>"; };
3D3417CD2E29D5D6006A988B /* BurningManGeoJSONMapConfig.json.zlib */ = {isa = PBXFileReference; lastKnownFileType = file; path = BurningManGeoJSONMapConfig.json.zlib; sourceTree = "<group>"; };
6D825E612C34786C008DBEE4 /* CommonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonRegex.swift; sourceTree = "<group>"; };
6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = "<group>"; };
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = "<group>"; };
@ -1055,12 +1057,10 @@
DDC2E18926CE24F70042C5E4 /* Resources */ = {
isa = PBXGroup;
children = (
3D3417CD2E29D5D6006A988B /* BurningManGeoJSONMapConfig.json.zlib */,
DDB75A192A05EB67006ED576 /* alpha.png */,
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */,
DD0E21002B8A6BC500F2D100 /* DeviceHardware.json */,
3D3417BB2E273AC6006A988B /* Street_Outlines.geojson */,
3D3417C12E274800006A988B /* Toilets.geojson */,
3D3417C22E274800006A988B /* Trash_Fence.geojson */,
);
path = Resources;
sourceTree = "<group>";
@ -1110,6 +1110,7 @@
DDC2E1A526CEB32B0042C5E4 /* Helpers */ = {
isa = PBXGroup;
children = (
3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */,
BCD7448C2E0F2FA300F265A2 /* ContactURLHandler.swift */,
DDD43FE12A78C86B0083A3E9 /* Mqtt */,
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */,
@ -1178,6 +1179,8 @@
DDDB443E29F79A9400EE2349 /* Extensions */ = {
isa = PBXGroup;
children = (
3D3417C92E29D3B0006A988B /* Color+Hex.swift */,
3D3417CA2E29D3B0006A988B /* Data+Gzip.swift */,
DD007BB12AA59B9A00F5FA12 /* CoreData */,
DDFFA7462B3A7F3C004730DB /* Bundle.swift */,
DDDB444529F8A96500EE2349 /* Character.swift */,
@ -1372,11 +1375,9 @@
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */,
25AECD4F2C2F723200862C8E /* Localizable.xcstrings in Resources */,
DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */,
3D3417BC2E273AC6006A988B /* Street_Outlines.geojson in Resources */,
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */,
3D3417C32E274800006A988B /* Toilets.geojson in Resources */,
3D3417C42E274800006A988B /* Trash_Fence.geojson in Resources */,
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */,
3D3417CE2E29D5D6006A988B /* BurningManGeoJSONMapConfig.json.zlib in Resources */,
DD0E21012B8A6F1300F2D100 /* DeviceHardware.json in Resources */,
DDDBC87B2BC62E4E001E8DF7 /* Settings.bundle in Resources */,
);
@ -1492,6 +1493,7 @@
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */,
3D3417B42E2730EC006A988B /* GeoJSONOverlayManager.swift in Sources */,
3D3417C82E29D38A006A988B /* GeoJSONOverlayConfig.swift in Sources */,
237B46962DC8F1C100B22D99 /* RateLimitedButton.swift in Sources */,
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */,
@ -1589,6 +1591,8 @@
DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */,
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */,
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
3D3417CB2E29D3B0006A988B /* Color+Hex.swift in Sources */,
3D3417CC2E29D3B0006A988B /* Data+Gzip.swift in Sources */,
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */,
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */,

View file

@ -0,0 +1,30 @@
import SwiftUI
extension Color {
/// Initialize a Color from a hex string (e.g., "#FF0000" or "FF0000")
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}

View file

@ -0,0 +1,63 @@
import Foundation
import Compression
extension Data {
/// Decompresses raw deflate data
func zlibDecompressed() throws -> Data {
guard self.count > 0 else { return Data() }
// Try Foundation's zlib first
do {
let decompressedData = try (self as NSData).decompressed(using: .zlib) as Data
print("Data+Zlib: Successfully decompressed with Foundation \(count) bytes to \(decompressedData.count) bytes")
return decompressedData
} catch {
print("Data+Zlib: Foundation decompression failed: \(error), trying raw deflate...")
}
// Fallback to Compression framework with raw deflate
let bufferSize = count * 10
let destination = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer { destination.deallocate() }
return try self.withUnsafeBytes { bytes in
let source = bytes.bindMemory(to: UInt8.self)
let result = compression_decode_buffer(
destination, bufferSize,
source.baseAddress!, count,
nil, COMPRESSION_ZLIB
)
guard result > 0 else {
print("Data+Zlib: Raw deflate decompression also failed, result size: \(result)")
throw ZlibError.decompression
}
print("Data+Zlib: Successfully decompressed with raw deflate \(count) bytes to \(result) bytes")
return Data(bytes: destination, count: result)
}
}
}
enum ZlibError: Error {
case decompression
var localizedDescription: String {
switch self {
case .decompression:
return "Failed to decompress data"
}
}
}
enum GzipError: Error {
case decompression
var localizedDescription: String {
switch self {
case .decompression:
return "Failed to decompress gzip data"
}
}
}

View file

@ -0,0 +1,123 @@
import Foundation
import MapKit
// MARK: - Configuration Models
struct GeoJSONOverlayConfiguration: Codable {
let version: String
let metadata: OverlayMetadata
let overlays: [OverlayDefinition]
}
struct OverlayMetadata: Codable {
let name: String
let description: String
let generated: String
}
struct OverlayDefinition: Codable {
let id: String
let name: String
let description: String
let rendering: RenderingProperties
let geojson: GeoJSONFeatureCollection
}
struct RenderingProperties: Codable {
let lineColor: String // Hex color (e.g., "#FF0000")
let lineOpacity: Double // 0.0 to 1.0
let lineThickness: Double // Line width in points
let fillOpacity: Double // 0.0 to 1.0
}
struct GeoJSONFeatureCollection: Codable {
let type: String // Always "FeatureCollection"
let features: [GeoJSONFeature]
}
struct GeoJSONFeature: Codable {
let type: String // Always "Feature"
let id: Int?
let geometry: GeoJSONGeometry
let properties: [String: AnyCodableValue]?
}
struct GeoJSONGeometry: Codable {
let type: String // "Point", "LineString", "Polygon", etc.
let coordinates: AnyCodableValue // Flexible coordinate structure
}
// MARK: - Flexible JSON Value Type
enum AnyCodableValue: Codable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case array([AnyCodableValue])
case object([String: AnyCodableValue])
case null
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Int.self) {
self = .int(value)
} else if let value = try? container.decode(Double.self) {
self = .double(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([AnyCodableValue].self) {
self = .array(value)
} else if let value = try? container.decode([String: AnyCodableValue].self) {
self = .object(value)
} else {
throw DecodingError.typeMismatch(AnyCodableValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to decode AnyCodableValue"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .bool(let value):
try container.encode(value)
case .int(let value):
try container.encode(value)
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
case .array(let value):
try container.encode(value)
case .object(let value):
try container.encode(value)
}
}
// Helper to convert coordinates to the format expected by MKGeoJSONDecoder
func toAnyObject() -> Any {
switch self {
case .null:
return NSNull()
case .bool(let value):
return value
case .int(let value):
return value
case .double(let value):
return value
case .string(let value):
return value
case .array(let values):
return values.map { $0.toAnyObject() }
case .object(let dict):
return dict.mapValues { $0.toAnyObject() }
}
}
}

View file

@ -1,39 +1,101 @@
import SwiftUI
import MapKit
/// Enum for supported static overlays
enum StaticGeoJSONOverlay: String, CaseIterable {
case streetOutlines = "Street_Outlines"
case toilets = "Toilets"
case trashFence = "Trash_Fence"
var filename: String { self.rawValue + ".geojson" }
}
/// Manager for loading and adding GeoJSON overlays
/// Manager for loading and managing GeoJSON overlays from consolidated configuration
class GeoJSONOverlayManager {
static let shared = GeoJSONOverlayManager()
private init() {}
private var overlays: [StaticGeoJSONOverlay: [MKOverlay]] = [:]
private var configuration: GeoJSONOverlayConfiguration?
private var overlays: [String: [MKOverlay]] = [:]
/// Load overlays for a given type (from bundle)
func loadOverlays(for type: StaticGeoJSONOverlay) -> [MKOverlay] {
print("GeoJSONOverlayManager: Attempting to load overlays for \(type.rawValue)")
if let cached = overlays[type] {
print("GeoJSONOverlayManager: Returning cached overlays for \(type.rawValue), count: \(cached.count)")
/// Load and decompress the consolidated configuration
func loadConfiguration() -> GeoJSONOverlayConfiguration? {
if let cached = configuration {
return cached
}
guard let url = Bundle.main.url(forResource: type.rawValue, withExtension: "geojson") else {
print("GeoJSONOverlayManager: No file found for \(type.rawValue).geojson")
guard let url = Bundle.main.url(forResource: "BurningManGeoJSONMapConfig", withExtension: "json.zlib") else {
print("GeoJSONOverlayManager: No compressed configuration file found")
return nil
}
do {
let compressedData = try Data(contentsOf: url)
print("GeoJSONOverlayManager: Loaded compressed data size: \(compressedData.count) bytes")
let decompressedData = try compressedData.zlibDecompressed()
print("GeoJSONOverlayManager: Decompressed data size: \(decompressedData.count) bytes")
// Debug: Check the first few characters of decompressed data
if let decompressedString = String(data: decompressedData, encoding: .utf8) {
let firstChars = String(decompressedString.prefix(100))
print("GeoJSONOverlayManager: First 100 chars of decompressed data: \(firstChars)")
} else {
print("GeoJSONOverlayManager: Decompressed data is not valid UTF-8")
// Show first few bytes as hex
let firstBytes = decompressedData.prefix(20).map { String(format: "%02x", $0) }.joined()
print("GeoJSONOverlayManager: First 20 bytes (hex): \(firstBytes)")
}
let config = try JSONDecoder().decode(GeoJSONOverlayConfiguration.self, from: decompressedData)
print("GeoJSONOverlayManager: Loaded configuration with \(config.overlays.count) overlays")
configuration = config
return config
} catch {
print("GeoJSONOverlayManager: Failed to load configuration: \(error)")
return nil
}
}
/// Load overlays for a specific overlay ID
func loadOverlays(for overlayId: String) -> [MKOverlay] {
print("GeoJSONOverlayManager: Attempting to load overlays for \(overlayId)")
if let cached = overlays[overlayId] {
print("GeoJSONOverlayManager: Returning cached overlays for \(overlayId), count: \(cached.count)")
return cached
}
guard let config = loadConfiguration() else {
print("GeoJSONOverlayManager: Failed to load configuration")
return []
}
print("GeoJSONOverlayManager: Found file at: \(url)")
guard let overlayDef = config.overlays.first(where: { $0.id == overlayId }) else {
print("GeoJSONOverlayManager: No overlay found for ID: \(overlayId)")
return []
}
do {
let data = try Data(contentsOf: url)
print("GeoJSONOverlayManager: Loaded data size: \(data.count) bytes")
let features = try MKGeoJSONDecoder().decode(data)
print("GeoJSONOverlayManager: Decoded \(features.count) features for \(type.rawValue)")
// Convert our custom GeoJSON structure to the format expected by MKGeoJSONDecoder
let standardGeoJSON: [String: Any] = [
"type": overlayDef.geojson.type,
"features": overlayDef.geojson.features.map { feature in
var featureDict: [String: Any] = [
"type": feature.type,
"geometry": [
"type": feature.geometry.type,
"coordinates": feature.geometry.coordinates.toAnyObject()
]
]
if let id = feature.id {
featureDict["id"] = id
}
if let properties = feature.properties {
featureDict["properties"] = properties.mapValues { $0.toAnyObject() }
}
return featureDict
}
]
let geojsonData = try JSONSerialization.data(withJSONObject: standardGeoJSON)
let features = try MKGeoJSONDecoder().decode(geojsonData)
print("GeoJSONOverlayManager: Decoded \(features.count) features for \(overlayId)")
var allOverlays: [MKOverlay] = []
for (index, feature) in features.enumerated() {
@ -53,23 +115,36 @@ class GeoJSONOverlayManager {
}
}
print("GeoJSONOverlayManager: Created \(allOverlays.count) total overlays for \(type.rawValue)")
overlays[type] = allOverlays
print("GeoJSONOverlayManager: Created \(allOverlays.count) total overlays for \(overlayId)")
overlays[overlayId] = allOverlays
return allOverlays
} catch {
print("Failed to load GeoJSON overlay: \(type) error: \(error)")
print("GeoJSONOverlayManager: Failed to decode overlays for \(overlayId): \(error)")
return []
}
}
/// Add overlays to the map
func addOverlays(_ type: StaticGeoJSONOverlay, to mapView: MKMapView) {
let overlays = loadOverlays(for: type)
mapView.addOverlays(overlays, level: .aboveLabels)
/// Get rendering properties for an overlay
func getRenderingProperties(for overlayId: String) -> RenderingProperties? {
guard let config = loadConfiguration() else { return nil }
return config.overlays.first(where: { $0.id == overlayId })?.rendering
}
/// Remove overlays from the map
func removeOverlays(_ type: StaticGeoJSONOverlay, from mapView: MKMapView) {
let overlays = self.overlays[type] ?? []
mapView.removeOverlays(overlays)
/// Get all available overlay IDs
func getAvailableOverlayIds() -> [String] {
guard let config = loadConfiguration() else { return [] }
return config.overlays.map { $0.id }
}
/// Get overlay definition by ID
func getOverlayDefinition(for overlayId: String) -> OverlayDefinition? {
guard let config = loadConfiguration() else { return nil }
return config.overlays.first(where: { $0.id == overlayId })
}
/// Clear cached overlays (useful for testing or memory management)
func clearCache() {
overlays.removeAll()
configuration = nil
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
{"type":"FeatureCollection","features":[{"type":"Feature","id":0,"geometry":{"type":"Polygon","coordinates":[[[-119.1828,40.80314],[-119.21777,40.80735],[-119.23383,40.78337],[-119.2088,40.76435],[-119.17727,40.77656],[-119.1828,40.80314]]]},"properties":{"FID":0,"Id":0}}]}

View file

@ -231,47 +231,37 @@ struct MeshMapContent: MapContent {
}
}
/// Burning Man GeoJSON Overlays
/// GeoJSON Overlays (Configuration-Driven)
if showBurningMan {
// Load and display street outlines
let streetOverlays = GeoJSONOverlayManager.shared.loadOverlays(for: .streetOutlines)
let streetIdentifiableOverlays = streetOverlays.map { IdentifiableOverlay(overlay: $0) }
ForEach(streetIdentifiableOverlays) { identifiable in
let overlay = identifiable.overlay
if let polygon = overlay as? MKPolygon {
MapPolygon(polygon)
.stroke(.green.opacity(0.8), lineWidth: 0.5)
.foregroundStyle(.clear)
} else if let polyline = overlay as? MKPolyline {
MapPolyline(polyline)
.stroke(.green.opacity(0.9), lineWidth: 0.8)
}
}
let overlayManager = GeoJSONOverlayManager.shared
let availableOverlays = overlayManager.getAvailableOverlayIds()
// Load and display toilets
let toiletOverlays = GeoJSONOverlayManager.shared.loadOverlays(for: .toilets)
let toiletIdentifiableOverlays = toiletOverlays.map { IdentifiableOverlay(overlay: $0) }
ForEach(toiletIdentifiableOverlays) { identifiable in
let overlay = identifiable.overlay
if let polygon = overlay as? MKPolygon {
MapPolygon(polygon)
.stroke(.blue, lineWidth: 2)
.foregroundStyle(.blue.opacity(1.0))
}
}
ForEach(availableOverlays, id: \.self) { overlayId in
let overlays = overlayManager.loadOverlays(for: overlayId)
let rendering = overlayManager.getRenderingProperties(for: overlayId)
// Load and display trash fence
let trashFenceOverlays = GeoJSONOverlayManager.shared.loadOverlays(for: .trashFence)
let trashFenceIdentifiableOverlays = trashFenceOverlays.map { IdentifiableOverlay(overlay: $0) }
ForEach(trashFenceIdentifiableOverlays) { identifiable in
let overlay = identifiable.overlay
if let polyline = overlay as? MKPolyline {
MapPolyline(polyline)
.stroke(.red, lineWidth: 2)
} else if let polygon = overlay as? MKPolygon {
MapPolygon(polygon)
.stroke(.red, lineWidth: 2)
.foregroundStyle(.clear)
ForEach(overlays.map { IdentifiableOverlay(overlay: $0) }) { identifiable in
let overlay = identifiable.overlay
if let polygon = overlay as? MKPolygon {
MapPolygon(polygon)
.stroke(
Color(hex: rendering?.lineColor ?? "#000000")
.opacity(rendering?.lineOpacity ?? 1.0),
lineWidth: rendering?.lineThickness ?? 1.0
)
.foregroundStyle(
Color(hex: rendering?.lineColor ?? "#000000")
.opacity(rendering?.fillOpacity ?? 0.0)
)
} else if let polyline = overlay as? MKPolyline {
MapPolyline(polyline)
.stroke(
Color(hex: rendering?.lineColor ?? "#000000")
.opacity(rendering?.lineOpacity ?? 1.0),
lineWidth: rendering?.lineThickness ?? 1.0
)
}
}
}
}