mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Somewhat working offline maps
This commit is contained in:
parent
28bfa99612
commit
e1bf4b0212
14 changed files with 183 additions and 230 deletions
|
|
@ -102,7 +102,7 @@
|
|||
DDB75A112A059258006ED576 /* Url.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A102A059258006ED576 /* Url.swift */; };
|
||||
DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */; };
|
||||
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A152A0594AD006ED576 /* TileOverlay.swift */; };
|
||||
DDB75A182A05975A006ED576 /* TilesDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A172A05975A006ED576 /* TilesDownloadView.swift */; };
|
||||
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; };
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; };
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; };
|
||||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; };
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
DDB75A102A059258006ED576 /* Url.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Url.swift; sourceTree = "<group>"; };
|
||||
DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineTileManager.swift; sourceTree = "<group>"; };
|
||||
DDB75A152A0594AD006ED576 /* TileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileOverlay.swift; sourceTree = "<group>"; };
|
||||
DDB75A172A05975A006ED576 /* TilesDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilesDownloadView.swift; sourceTree = "<group>"; };
|
||||
DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = "<group>"; };
|
||||
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -559,7 +559,6 @@
|
|||
children = (
|
||||
DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */,
|
||||
DDB75A152A0594AD006ED576 /* TileOverlay.swift */,
|
||||
DDB75A172A05975A006ED576 /* TilesDownloadView.swift */,
|
||||
);
|
||||
path = Map;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -661,6 +660,7 @@
|
|||
DDC2E18926CE24F70042C5E4 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDB75A192A05EB67006ED576 /* alpha.png */,
|
||||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */,
|
||||
);
|
||||
path = Resources;
|
||||
|
|
@ -908,6 +908,7 @@
|
|||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */,
|
||||
DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */,
|
||||
DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */,
|
||||
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */,
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -971,7 +972,6 @@
|
|||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
DDB75A182A05975A006ED576 /* TilesDownloadView.swift in Sources */,
|
||||
DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "alpha.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +128,27 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MapLayer: String, CaseIterable, Equatable {
|
||||
case standard
|
||||
case hybrid
|
||||
case satellite
|
||||
case offline
|
||||
var localized: String { self.rawValue.localized }
|
||||
var zoomRange: [Int] {
|
||||
switch self {
|
||||
case .standard:
|
||||
return [Int](0...24)
|
||||
case .hybrid:
|
||||
return [Int](0...24)
|
||||
case .satellite:
|
||||
return [Int](0...24)
|
||||
case .offline:
|
||||
return [Int](0...17)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MapTileServerLinks: Int, CaseIterable, Identifiable {
|
||||
|
||||
case none = 0
|
||||
|
|
@ -159,16 +180,4 @@ enum MapTileServerLinks: Int, CaseIterable, Identifiable {
|
|||
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
|
||||
}
|
||||
}
|
||||
var zoomRange: [Int] {
|
||||
switch self {
|
||||
case .none:
|
||||
return [Int](0...1)
|
||||
case .wikimedia:
|
||||
return [Int](0...24)
|
||||
case .openStreetMaps:
|
||||
return [Int](0...24)
|
||||
case .nationalMap:
|
||||
return [Int](0...24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ extension UserDefaults {
|
|||
case preferredPeripheralId
|
||||
case provideLocation
|
||||
case provideLocationInterval
|
||||
case meshMapType
|
||||
case meshMapCenteringMode
|
||||
//case meshMapType
|
||||
case meshMapRecentering
|
||||
case meshMapCustomTileServer
|
||||
case meshMapShowNodeHistory
|
||||
case meshMapShowRouteLines
|
||||
case enableOfflineMaps
|
||||
case mapTileServer
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
|
@ -74,12 +74,21 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
static var mapType: Int {
|
||||
// static var mapType: Int {
|
||||
// get {
|
||||
// UserDefaults.standard.integer(forKey: "meshMapType")
|
||||
// }
|
||||
// set {
|
||||
// UserDefaults.standard.set(newValue, forKey: "meshMapType")
|
||||
// }
|
||||
// }
|
||||
|
||||
static var mapLayer: MapLayer {
|
||||
get {
|
||||
UserDefaults.standard.integer(forKey: "meshMapType")
|
||||
MapLayer(rawValue: UserDefaults.standard.string(forKey: "mapLayer") ?? MapLayer.standard.rawValue) ?? MapLayer.standard
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "meshMapType")
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapLayer")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,15 +22,15 @@ class OfflineTileManager: ObservableObject {
|
|||
}
|
||||
|
||||
// MARK: - Private properties
|
||||
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer) }
|
||||
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.count > 1 ? UserDefaults.mapTileServer : "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png") }
|
||||
|
||||
private var documentsDirectory: URL { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
|
||||
// MARK: - Public property
|
||||
@Published var progress: Float = 0
|
||||
@Published var status: DownloadStatus = .download
|
||||
var progress: Float = 0
|
||||
var status: DownloadStatus = .download
|
||||
|
||||
// MARK: - Public methods
|
||||
func getAllDownloadedSize() -> String {
|
||||
|
|
@ -100,7 +100,7 @@ class OfflineTileManager: ObservableObject {
|
|||
if fileManager.fileExists(atPath: tilesUrl.path){
|
||||
return tilesUrl
|
||||
} else {
|
||||
if !UserDefaults.enableOfflineMaps { // Get and persist newTile
|
||||
if UserDefaults.enableOfflineMaps { // Get and persist newTile
|
||||
return persistLocally(path: path)
|
||||
} else { // Else display empty tile (transparent over Maps tiles)
|
||||
return Bundle.main.url(forResource: "alpha", withExtension: "png")!
|
||||
|
|
@ -140,7 +140,7 @@ class OfflineTileManager: ObservableObject {
|
|||
let data = try Data(contentsOf: url)
|
||||
try data.write(to: filename)
|
||||
} catch {
|
||||
print("❤️ PersistLocallyError = \(error)")
|
||||
print("💀 Save Tile Error = \(error)")
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
//
|
||||
// TilesDownloadView.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright © Garth Vander Houwen 5/5/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
struct TilesDownloadView: View {
|
||||
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
@State private var showAlert = false
|
||||
@State var otherDownloadInProgress = false
|
||||
|
||||
var boundingBox: MKMapRect
|
||||
var name: String
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
Button(action: {
|
||||
if self.tileManager.status == .download {
|
||||
//Feedback.selected()
|
||||
self.tileManager.download(boundingBox: self.boundingBox, name: self.name)
|
||||
} else if self.tileManager.status == .downloaded {
|
||||
//Feedback.selected()
|
||||
self.showAlert = true
|
||||
}
|
||||
}) {
|
||||
HStack() {
|
||||
if tileManager.status == .downloaded {
|
||||
Image(systemName: "trash")
|
||||
.accentColor(.red)
|
||||
} else {
|
||||
Image(systemName: "map")
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
if tileManager.status == .download {
|
||||
Text("\("map.tiles.download".localized) (\(tileManager.getEstimatedDownloadSize(for: boundingBox).toBytes))")
|
||||
} else if tileManager.status == .downloading {
|
||||
Text("\("map.tiles.downloading".localized) (\(tileManager.getEstimatedDownloadSize(for: boundingBox).toBytes) \("Left".localized))")
|
||||
} else {
|
||||
Text("\("map.tiles.delete".localized) (\(tileManager.getDownloadedSize(for: boundingBox).toBytes))")
|
||||
.accentColor(.red)
|
||||
}
|
||||
if tileManager.status == .downloading {
|
||||
ProgressView(value: tileManager.progress)
|
||||
.frame(height: 10)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
//.isHidden(otherDownloadInProgress, remove: true)
|
||||
}
|
||||
.onAppear {
|
||||
guard self.tileManager.status != .downloading else {
|
||||
self.otherDownloadInProgress = true
|
||||
return
|
||||
}
|
||||
self.tileManager.status = self.tileManager.hasBeenDownloaded(for: self.boundingBox) ? .downloaded : .download
|
||||
}
|
||||
.actionSheet(isPresented: $showAlert) {
|
||||
ActionSheet(
|
||||
title: Text("\("Delete".localized) (\(self.tileManager.getDownloadedSize(for: boundingBox).toBytes))"),
|
||||
message: Text("DeleteTiles".localized),
|
||||
buttons: [
|
||||
.destructive(Text("Delete".localized), action: { self.tileManager.remove(for: self.boundingBox) }),
|
||||
.cancel(Text("Cancel".localized))
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
struct TilesRow_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
TilesDownloadView(boundingBox: MKMapRect(), name: "test")
|
||||
.previewLayout(.fixed(width: 300, height: 80))
|
||||
.environment(\.colorScheme, .light)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
|
@ -10,29 +10,30 @@ import MapKit
|
|||
func degreesToRadians(_ number: Double) -> Double {
|
||||
return number * .pi / 180
|
||||
}
|
||||
var currentMapLayer: MapLayer?
|
||||
|
||||
struct MapViewSwiftUI: UIViewRepresentable {
|
||||
|
||||
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
@Binding var visibleMapRect: MKMapRect
|
||||
|
||||
let mapView = MKMapView()
|
||||
// Parameters
|
||||
var selectedMapLayer: MapLayer
|
||||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
let mapViewType: MKMapType
|
||||
|
||||
let userTrackingMode: MKUserTrackingMode
|
||||
let showNodeHistory: Bool
|
||||
let showRouteLines: Bool
|
||||
|
||||
let mapViewType: MKMapType = MKMapType.standard
|
||||
|
||||
// Offline Map Tiles
|
||||
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
@State private var loadedLastUpdatedLocalMapFile = 0
|
||||
var customMapOverlay: CustomMapOverlay?
|
||||
@State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
// Custom Tile Server
|
||||
var tileRenderer: MKTileOverlayRenderer?
|
||||
let tileServer: MapTileServerLinks = .openStreetMaps
|
||||
|
||||
// MARK: Private methods
|
||||
|
||||
|
|
@ -89,7 +90,65 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
#endif
|
||||
}
|
||||
|
||||
private func setOverlays(mapView: MKMapView) {
|
||||
// Avoid refreshing UI if selectedLayer has not changed
|
||||
guard currentMapLayer != selectedMapLayer else { return }
|
||||
currentMapLayer = selectedMapLayer
|
||||
for overlay in mapView.overlays {
|
||||
if overlay is TileOverlay {
|
||||
mapView.removeOverlay(overlay)
|
||||
}
|
||||
}
|
||||
switch selectedMapLayer {
|
||||
case .offline:
|
||||
let overlay = TileOverlay()
|
||||
overlay.canReplaceMapContent = false
|
||||
mapView.mapType = .standard
|
||||
mapView.addOverlay(overlay, level: .aboveRoads)
|
||||
case .satellite:
|
||||
mapView.mapType = .satellite
|
||||
case .hybrid:
|
||||
mapView.mapType = .hybrid
|
||||
default:
|
||||
mapView.mapType = .standard
|
||||
}
|
||||
}
|
||||
|
||||
private func setUserTracking(mapView: MKMapView, headingView: UIImageView?) {
|
||||
// switch selectedTracking {
|
||||
// case .bounding:
|
||||
// guard let firstBoundingBox = trails.first?.polyline.boundingMapRect else {
|
||||
// self.selectedTracking = .enabled
|
||||
// return
|
||||
// }
|
||||
// let boundingBox = trails
|
||||
// .map { $0.polyline.boundingMapRect }
|
||||
// .reduce(firstBoundingBox) { (boundingBox, nextResult) -> MKMapRect in
|
||||
// let minX = nextResult.minX < boundingBox.minX ? nextResult.minX : boundingBox.minX
|
||||
// let maxX = nextResult.maxX > boundingBox.maxX ? nextResult.maxX : boundingBox.maxX
|
||||
// let minY = nextResult.minY < boundingBox.minY ? nextResult.minY : boundingBox.minY
|
||||
// let maxY = nextResult.maxY > boundingBox.maxY ? nextResult.maxY : boundingBox.maxY
|
||||
// return MKMapRect(origin: MKMapPoint(x: minX, y: minY), size: MKMapSize(width: maxX-minX, height: maxY-minY))
|
||||
// }
|
||||
// var region = MKCoordinateRegion(boundingBox)
|
||||
// region.span.latitudeDelta += 0.01
|
||||
// region.span.longitudeDelta += 0.01
|
||||
// mapView.setRegion(region, animated: false)
|
||||
// case .disabled:
|
||||
// mapView.setUserTrackingMode(.none, animated: true)
|
||||
// locationManager.updateHeading = false
|
||||
// headingView?.isHidden = true
|
||||
// case .enabled:
|
||||
// mapView.setUserTrackingMode(.follow, animated: true)
|
||||
// locationManager.updateHeading = true
|
||||
// case .heading:
|
||||
// mapView.setUserTrackingMode(.followWithHeading, animated: true)
|
||||
// headingView?.isHidden = true
|
||||
// }
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> MKMapView {
|
||||
currentMapLayer = nil
|
||||
mapView.delegate = context.coordinator
|
||||
self.configureMap(mapView: mapView)
|
||||
return mapView
|
||||
|
|
@ -97,65 +156,39 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
|
||||
func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
|
||||
mapView.mapType = mapViewType
|
||||
//visibleMapRect = mapView.visibleMapRect
|
||||
|
||||
// Offline maps and tile server settings
|
||||
if false {// UserDefaults.enableOfflineMaps {
|
||||
// MBTiles Offline maps and tile server settings
|
||||
// if UserDefaults.enableOfflineMaps && UserDefaults.mapTileServer == "" {
|
||||
|
||||
visibleMapRect = mapView.visibleMapRect
|
||||
|
||||
if UserDefaults.mapTileServer.count > 0 {
|
||||
tileRenderer?.alpha = 0.0
|
||||
let overlays = mapView.overlays
|
||||
if mapView.mapType == .standard {
|
||||
let overlay = MKTileOverlay(urlTemplate: UserDefaults.mapTileServer)
|
||||
if overlays.contains(where: {$0 is MKPolyline}) {
|
||||
mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
if let poly_overlay = overlays.filter({$0 is MKPolyline}).first {
|
||||
mapView.addOverlay(poly_overlay, level: .aboveLabels)
|
||||
}
|
||||
} else {
|
||||
mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
|
||||
}
|
||||
} else {
|
||||
for overlay in overlays {
|
||||
if let ove = overlay as? MKTileOverlay {
|
||||
mapView.removeOverlay(ove)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
|
||||
mapView.removeOverlays(mapView.overlays)
|
||||
if self.customMapOverlay != nil {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
|
||||
if fileManager.fileExists(atPath: tilePath) {
|
||||
print("Loading local map file")
|
||||
if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) {
|
||||
overlay.canReplaceMapContent = false// customMapOverlay.canReplaceMapContent
|
||||
mapView.addOverlay(overlay)
|
||||
}
|
||||
} else {
|
||||
print("Couldn't find a local map file to load")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.presentCustomMapOverlayHash = self.customMapOverlay
|
||||
self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile
|
||||
}
|
||||
}
|
||||
}
|
||||
// if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
|
||||
// mapView.removeOverlays(mapView.overlays)
|
||||
// if self.customMapOverlay != nil {
|
||||
//
|
||||
// let fileManager = FileManager.default
|
||||
// let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
// let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
|
||||
// if fileManager.fileExists(atPath: tilePath) {
|
||||
// print("Loading local map file")
|
||||
// if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) {
|
||||
// overlay.canReplaceMapContent = true// customMapOverlay.canReplaceMapContent
|
||||
// mapView.addOverlay(overlay)
|
||||
// }
|
||||
// } else {
|
||||
// print("Couldn't find a local map file to load")
|
||||
// }
|
||||
// }
|
||||
// DispatchQueue.main.async {
|
||||
// self.presentCustomMapOverlayHash = self.customMapOverlay
|
||||
// self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let latest = positions
|
||||
.filter { $0.latest == true }
|
||||
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
|
||||
// Node Route Lines
|
||||
if showRouteLines {
|
||||
if true {//showRouteLines {
|
||||
// Remove all existing PolyLine Overlays
|
||||
for overlay in mapView.overlays {
|
||||
if overlay is MKPolyline {
|
||||
|
|
@ -188,6 +221,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set selected map layer
|
||||
// setOverlays(mapView: mapView)
|
||||
|
||||
let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
|
||||
if annotationCount != mapView.annotations.count {
|
||||
print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
|
||||
|
|
@ -240,7 +276,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
|
||||
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
|
||||
|
||||
print (mapView.visibleMapRect)
|
||||
//print (mapView.visibleMapRect)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ struct NodeDetail: View {
|
|||
@AppStorage("meshMapType") private var meshMapType = 0
|
||||
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
|
||||
@State private var mapType: MKMapType = .standard
|
||||
//@State private var mapType: MKMapType = .standard
|
||||
@State private var selectedMapLayer: MapLayer = .standard
|
||||
@State var mapRect: MKMapRect = MKMapRect()
|
||||
@State var waypointCoordinate: WaypointCoordinate?
|
||||
@State var editingWaypoint: Int = 0
|
||||
|
|
@ -70,10 +71,11 @@ struct NodeDetail: View {
|
|||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
}
|
||||
},
|
||||
visibleMapRect: $mapRect,
|
||||
//visibleMapRect: $mapRect,
|
||||
selectedMapLayer: selectedMapLayer,
|
||||
positions: lastTenThousand,
|
||||
waypoints: Array(waypoints),
|
||||
mapViewType: mapType,
|
||||
//mapViewType: mapType,
|
||||
userTrackingMode: MKUserTrackingMode.none,
|
||||
showNodeHistory: meshMapShowNodeHistory,
|
||||
showRouteLines: meshMapShowRouteLines,
|
||||
|
|
@ -83,18 +85,18 @@ struct NodeDetail: View {
|
|||
Spacer()
|
||||
HStack(alignment: .bottom, spacing: 1) {
|
||||
|
||||
Picker("Map Type", selection: $mapType) {
|
||||
ForEach(MeshMapTypes.allCases) { map in
|
||||
Text(map.description)
|
||||
.tag(map.MKMapTypeValue())
|
||||
}
|
||||
}
|
||||
.onChange(of: (mapType)) { newMapType in
|
||||
UserDefaults.mapType = Int(newMapType.rawValue)
|
||||
}
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.pickerStyle(.menu)
|
||||
.padding(5)
|
||||
// Picker("Map Type", selection: $mapType) {
|
||||
// ForEach(MeshMapTypes.allCases) { map in
|
||||
// Text(map.description)
|
||||
// .tag(map.MKMapTypeValue())
|
||||
// }
|
||||
// }
|
||||
// .onChange(of: (mapType)) { newMapType in
|
||||
// UserDefaults.mapType = Int(newMapType.rawValue)
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .pickerStyle(.menu)
|
||||
// .padding(5)
|
||||
VStack {
|
||||
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
.font(.caption)
|
||||
|
|
@ -229,7 +231,7 @@ struct NodeDetail: View {
|
|||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
mapType = MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard
|
||||
//mapType = .standard// MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard
|
||||
}
|
||||
.task(id: node.num) {
|
||||
if !loadedWeather {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ struct NodeList: View {
|
|||
let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: 20, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
|
||||
let characters = Array(node.user?.shortName ?? "??")
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: characters.count == 1 ? 40 : 20, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
|
||||
.padding(.trailing, 5)
|
||||
VStack(alignment: .leading) {
|
||||
Text(node.user?.longName ?? "unknown".localized).font(.headline)
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ struct NodeMap: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@State var meshMapType: Int = UserDefaults.mapType
|
||||
@State var selectedMapLayer: MapLayer = UserDefaults.mapLayer
|
||||
@State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering
|
||||
@State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines
|
||||
@State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins
|
||||
@State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps
|
||||
@State var mapTileServer: String = UserDefaults.mapTileServer
|
||||
@State var mapRect: MKMapRect = MKMapRect()
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
|
||||
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
|
||||
|
|
@ -32,14 +31,12 @@ struct NodeMap: View {
|
|||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
@State var waypointCoordinate: WaypointCoordinate?
|
||||
|
||||
|
||||
@State var mapType: MKMapType = .standard
|
||||
@State var selectedTracking: UserTrackingModes = .none
|
||||
@State var selectedTileServer: MapTileServerLinks = .wikimedia
|
||||
@State var isPresentingInfoSheet: Bool = false
|
||||
|
||||
@State var waypointCoordinate: WaypointCoordinate?
|
||||
@State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
mapName: "offlinemap",
|
||||
tileType: "png",
|
||||
|
|
@ -59,10 +56,9 @@ struct NodeMap: View {
|
|||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
}
|
||||
},
|
||||
visibleMapRect: $mapRect,
|
||||
selectedMapLayer: selectedMapLayer,
|
||||
positions: Array(positions),
|
||||
waypoints: Array(waypoints),
|
||||
mapViewType: mapType,
|
||||
userTrackingMode: selectedTracking.MKUserTrackingModeValue(),
|
||||
showNodeHistory: enableMapNodeHistoryPins,
|
||||
showRouteLines: enableMapRouteLines,
|
||||
|
|
@ -92,15 +88,21 @@ struct NodeMap: View {
|
|||
VStack {
|
||||
Form {
|
||||
Section(header: Text("Map Options")) {
|
||||
Picker("Map Type", selection: $mapType) {
|
||||
ForEach(MeshMapTypes.allCases) { map in
|
||||
Text(map.description).tag(map.MKMapTypeValue())
|
||||
Picker(selection: $selectedMapLayer, label: Text("")) {
|
||||
ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
|
||||
Text(layer.localized)
|
||||
} else if layer != MapLayer.offline {
|
||||
Text(layer.localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (mapType)) { newMapType in
|
||||
UserDefaults.mapType = Int(newMapType.rawValue)
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
UserDefaults.mapLayer = newMapLayer
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
Toggle(isOn: $enableMapRecentering) {
|
||||
|
||||
|
|
@ -141,16 +143,16 @@ struct NodeMap: View {
|
|||
self.enableOfflineMaps.toggle()
|
||||
UserDefaults.enableOfflineMaps = self.enableOfflineMaps
|
||||
}
|
||||
Text("If you have shared a MBTiles file with meshtastic it will be loaded.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
// Text("If you have shared a MBTiles file with meshtastic it will be loaded.")
|
||||
// .font(.caption)
|
||||
// .foregroundColor(.gray)
|
||||
|
||||
if UserDefaults.enableOfflineMaps {
|
||||
VStack {
|
||||
// Picker("Tile Servers", selection: $selectedTileServer) {
|
||||
// ForEach(MapTileServerLinks.allCases) { ts in
|
||||
// Text(ts.description)
|
||||
// .tag(ts.id)
|
||||
// // .tag(ts.id)
|
||||
// }
|
||||
// }
|
||||
// .pickerStyle(.menu)
|
||||
|
|
@ -159,7 +161,7 @@ struct NodeMap: View {
|
|||
// mapTileServer = selectedTileServer.tileUrl
|
||||
// }
|
||||
|
||||
TilesDownloadView(boundingBox: mapRect, name: "All tiles")
|
||||
// TilesDownloadView(boundingBox: mapRect, name: "All tiles")
|
||||
HStack {
|
||||
Label("Tile Server", systemImage: "square.grid.3x2")
|
||||
TextField(
|
||||
|
|
@ -207,8 +209,6 @@ struct NodeMap: View {
|
|||
.onAppear(perform: {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
self.bleManager.context = context
|
||||
mapType = MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard
|
||||
|
||||
})
|
||||
.onDisappear(perform: {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@
|
|||
"not.connected"="Kein Gerät verbunden";
|
||||
"numbers.punctuation"="Ziffern und Interpunktion";
|
||||
"off"="Aus";
|
||||
"offline"="Offline";
|
||||
"on.boot"="Nur beim Starten";
|
||||
"options"="Optionen";
|
||||
"password"="Passwort";
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@
|
|||
"not.connected"="No device connected";
|
||||
"numbers.punctuation"="Numbers and Punctuation";
|
||||
"off"="Off";
|
||||
"offline"="Offline";
|
||||
"on.boot"="On Boot Only";
|
||||
"options"="Options";
|
||||
"password"="Password";
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@
|
|||
"not.connected"="未连接到电台";
|
||||
"numbers.punctuation"="数字和标点符号";
|
||||
"off"="关闭";
|
||||
"offline"="Offline";
|
||||
"on.boot"="仅在启动时";
|
||||
"options"="选项";
|
||||
"password"="密码";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue