* New Tile Server

* More Emoji
* Signal Strength indicators for lora
* Restrict tile overlays to zoom levels
This commit is contained in:
Garth Vander Houwen 2023-05-10 13:44:27 -07:00
parent c425486658
commit 255e10bbe5
15 changed files with 111 additions and 68 deletions

View file

@ -148,6 +148,7 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
case usgsTopo
case usgsImageryTopo
case usgsImageryOnly
case terrain
case toner
case watercolor
var id: String { self.rawValue }
@ -173,8 +174,10 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
return "[USGS](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer) [National Map](http://nationalmap.gov/) imagery and topographic overlay."
case .usgsImageryOnly:
return "[USGS](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer) [National Map](http://nationalmap.gov/) imagery only overlay."
case .terrain:
return "[Map Tiles](http://maps.stamen.com/#terrain/) by [Stamen Design](https://stamen.com), under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). Data © [OpenStreetMap](http://www.openstreetmap.org) contributors under [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)."
case .toner:
return "[Stamen Design's](https://github.com/stamen/toner-carto) black and white map tiles."
return "[Map Tiles](http://maps.stamen.com/#toner/) by [Stamen Design](https://stamen.com), under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). Data © [OpenStreetMap](http://www.openstreetmap.org) contributors under [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)."
case .watercolor:
return "Cooper Hewitt, Smithsonian Design Museum's [Watercolor Maptiles](https://watercolormaps.collection.cooperhewitt.org/) is a open-source mapping tool created by Stamen Design and built on [OpenStreetMap](http://www.openstreetmap.org) data."
}
@ -199,6 +202,8 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
return "USGS Topo Imagery"
case .usgsImageryOnly:
return "USGS Imagery Only"
case .terrain:
return "Terrain"
case .toner:
return "Toner"
case .watercolor:
@ -225,6 +230,8 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"
case .usgsImageryOnly:
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
case .terrain:
return "https://stamen-tiles-d.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png"
case .toner:
return "https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png"
case .watercolor:
@ -252,6 +259,8 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
return [Int](6...15)
case .usgsImageryOnly:
return [Int](6...15)
case .terrain:
return [Int](0...15)
case .toner:
return [Int](0...18)
case .watercolor:

View file

@ -52,11 +52,11 @@ public extension FileManager {
do {
accumulatedSize += try contentItemURL.regularFileAllocatedSize()
} catch {
print("❤️ \(error.localizedDescription)")
print("💥 File Manager Error: \(error.localizedDescription)")
}
}
if let error = enumeratorError { print("❤️ AllocatedSizeOfDirectory enumeratorError = \(error.localizedDescription)") }
if let error = enumeratorError { print("💥 AllocatedSizeOfDirectory enumeratorError = \(error.localizedDescription)") }
return Double(accumulatedSize).toBytes

View file

@ -49,12 +49,10 @@ struct Connect: View {
Section(header: Text("connected.radio").font(.title)) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right")
.resizable()
.symbolRenderingMode(.hierarchical)
.foregroundColor(.green)
.frame(width: 60, height: 60)
.padding(.trailing)
VStack(alignment: .center) {
CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 80, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : 30, textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white )
}
.padding(.trailing)
VStack(alignment: .leading) {
if node != nil {
Text(bleManager.connectedPeripheral.longName).font(.title2)
@ -74,7 +72,8 @@ struct Connect: View {
}
}
}
.font(.caption).foregroundColor(Color.gray)
.font(.caption)
.foregroundColor(Color.gray)
.padding([.top, .bottom])
.swipeActions {

View file

@ -8,7 +8,7 @@ import SwiftUI
struct ConnectedDevice: View {
var bluetoothOn: Bool
var deviceConnected: Bool
var name: String?
var name: String
var body: some View {
@ -20,7 +20,7 @@ struct ConnectedDevice: View {
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name!).font(.callout).foregroundColor(.gray)
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")

View file

@ -69,3 +69,34 @@ func getLoRaSignalStrength(snr: Float, rssi: Int32) -> LoRaSignalStrength {
return .fair
}
}
func getRssiColor(rssi: Int32) -> Color {
if rssi > -115 {
/// Good
return .green
} else if rssi > -115 && rssi < -120 {
/// Fair
return .yellow
} else if rssi > -126 {
/// Bad
return .orange
} else {
// None
return .red
}
}
func getSnrColor(snr: Float) -> Color {
if snr > -7 {
/// Good
return .green
} else if snr < -7 && snr > -13 {
/// Fair
return .yellow
} else if snr >= -14 {
/// Bad
return .orange
} else {
return .red
}
}

View file

@ -51,10 +51,10 @@ struct NodeInfoView: View {
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.title)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(.gray)
.foregroundColor(getSnrColor(snr: node.snr))
.font(.title3)
Text("RSSI \(node.rssi)dB")
.foregroundColor(.gray)
.foregroundColor(getRssiColor(rssi: node.rssi))
.font(.title3)
}
Divider()
@ -141,35 +141,35 @@ struct NodeInfoView: View {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 42 : 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
}
Divider()
VStack {
if node.user != nil {
if node.user != nil {
Divider()
VStack {
Image(node.user!.hwModel ?? "unset".localized)
.resizable()
.frame(width: 75, height: 75)
.cornerRadius(5)
Text(String(node.user!.hwModel ?? "unset".localized))
.font(.caption).fixedSize()
.font(.caption2).fixedSize()
}
}
Divider()
if node.snr != 0 {
Divider()
VStack(alignment: .center) {
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(.gray)
.foregroundColor(getSnrColor(snr: node.snr))
.font(.caption2)
Text("RSSI \(node.rssi)dB")
.foregroundColor(.gray)
.foregroundColor(getRssiColor(rssi: node.rssi))
.font(.caption2)
}
Divider()
}
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
Divider()
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))

View file

@ -105,8 +105,8 @@ struct MapViewSwiftUI: UIViewRepresentable {
if !UserDefaults.enableOfflineMapsMBTiles {
let overlay = TileOverlay()
overlay.canReplaceMapContent = false
//overlay.minimumZ = 0
//overlay.maximumZ = 17
overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex
overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex
mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads)
}
case .satellite:
@ -199,7 +199,6 @@ struct MapViewSwiftUI: UIViewRepresentable {
print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(waypoints)
mapView.addAnnotations(showNodeHistory ? positions : latest)
}
if userTrackingMode == MKUserTrackingMode.none {
mapView.showsUserLocation = false
@ -209,14 +208,15 @@ struct MapViewSwiftUI: UIViewRepresentable {
if latest.count == 1 {
mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
} else {
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.fitAllAnnotations()
}
}
} else {
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.showsUserLocation = true
}
mapView.setUserTrackingMode(userTrackingMode, animated: true)
}
@ -381,7 +381,6 @@ struct MapViewSwiftUI: UIViewRepresentable {
if view.tag > 0 {
parent.onWaypointEdit(view.tag)
}
print(waypointAnnotation)
default: break
}

View file

@ -204,7 +204,6 @@ struct UserMessageList: View {
.id(message.messageId)
.alert(isPresented: $showDeleteMessageAlert) {
Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."), primaryButton: .destructive(Text("Delete")) {
print("OK button tapped")
if deleteMessageId > 0 {
let message = user.messageList.first(where: { $0.messageId == deleteMessageId })
context.delete(message!)

View file

@ -119,11 +119,13 @@ struct DeviceMetricsLog: View {
} else {
ScrollView {
let columns = [
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
//GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 45), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 70), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 65), spacing: 0.1),
GridItem(spacing: 0)
GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1)
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
GridRow {
@ -159,7 +161,7 @@ struct DeviceMetricsLog: View {
Text("\(String(format: "%.2f", dm.airUtilTx))%")
.font(.caption)
Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time")
.font(.caption2)
.font(.caption)
}
}
}
@ -177,7 +179,8 @@ struct DeviceMetricsLog: View {
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.trailing)
.confirmationDialog(
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
@ -200,7 +203,8 @@ struct DeviceMetricsLog: View {
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.trailing)
}
.navigationTitle("device.metrics.log")
.navigationBarTitleDisplayMode(.inline)

View file

@ -108,10 +108,10 @@ struct EnvironmentMetricsLog: View {
} else {
ScrollView {
let columns = [
GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1),
GridItem(spacing: 0)
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1, pinnedViews: [.sectionHeaders]) {
@ -146,7 +146,7 @@ struct EnvironmentMetricsLog: View {
Text("\(String(format: "%.2f", em.gasResistance))")
.font(.caption)
Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
.font(.caption2)
.font(.caption)
}
}
}
@ -160,12 +160,13 @@ struct EnvironmentMetricsLog: View {
Button(role: .destructive) {
isPresentingClearLogConfirm = true
} label: {
Label("Clear Log", systemImage: "trash.fill")
Label("clear.log", systemImage: "trash.fill")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.trailing)
.confirmationDialog(
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
@ -186,7 +187,8 @@ struct EnvironmentMetricsLog: View {
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.leading)
}
.navigationTitle("Environment Metrics Log")
.navigationBarTitleDisplayMode(.inline)

View file

@ -38,13 +38,15 @@ struct NodeList: View {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, 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)
Text(node.user?.longName ?? "unknown".localized)
.fontWeight(.medium)
.font(.callout)
if connected {
HStack(alignment: .bottom) {
HStack {
Image(systemName: "repeat.circle.fill")
.font(.title3)
.font(.callout)
.symbolRenderingMode(.hierarchical)
Text("connected").font(.subheadline)
Text("connected").font(.callout)
.foregroundColor(.green)
}
}
@ -56,28 +58,27 @@ struct NodeList: View {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
.font(.title3)
.font(.footnote)
.symbolRenderingMode(.hierarchical)
DistanceText(meters: metersAway).font(.subheadline)
DistanceText(meters: metersAway).font(.footnote)
}
}
}
if node.channel > 0 {
HStack(alignment: .bottom) {
Image(systemName: "fibrechannel")
.font(.title3)
.font(.footnote)
.symbolRenderingMode(.hierarchical)
Text("Channel: \(node.channel)")
.font(.subheadline)
.font(.footnote)
}
}
HStack(alignment: .bottom) {
Image(systemName: "clock.badge.checkmark.fill")
.font(.title3)
.font(.caption)
.symbolRenderingMode(.hierarchical)
LastHeardText(lastHeard: node.lastHeard)
.font(.subheadline)
.font(.caption)
}
}
.frame(maxWidth: .infinity, alignment: .leading)

View file

@ -166,7 +166,6 @@ struct NodeMap: View {
.pickerStyle(DefaultPickerStyle())
.onChange(of: (selectedTileServer)) { newSelectedTileServer in
UserDefaults.mapTileServer = newSelectedTileServer
//tileManager.removeAll()
selectedMapLayer = .standard
}
Text("Attribution:")

View file

@ -121,12 +121,13 @@ struct PositionLog: View {
Button(role: .destructive) {
isPresentingClearLogConfirm = true
} label: {
Label("Clear Log", systemImage: "trash.fill")
Label("clear.log", systemImage: "trash.fill")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.trailing)
.confirmationDialog(
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
@ -143,10 +144,8 @@ struct PositionLog: View {
}
Button {
exportString = positionToCsvFile(positions: node.positions!.array as? [PositionEntity] ?? [])
isExporting = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
@ -154,7 +153,8 @@ struct PositionLog: View {
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.padding(.bottom)
.padding(.leading)
}
.fileExporter(
isPresented: $isExporting,

View file

@ -35,29 +35,30 @@ struct AppSettings: View {
let speed = Measurement(value: locationHelper.locationManager.location?.speed ?? 0, unit: UnitSpeed.kilometersPerHour)
HStack {
Label("Accuracy \(accuracy.formatted())", systemImage: "scope")
.font(.callout)
.font(.footnote)
Label("Sats \(LocationHelper.satsInView)", systemImage: "sparkles")
.font(.callout)
.font(.footnote)
}
Label("Coordinates \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.longitude ?? 0))", systemImage: "mappin")
.font(.callout)
Label("Coordinate \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.longitude ?? 0))", systemImage: "mappin")
.font(.footnote)
.textSelection(.enabled)
if locationHelper.locationManager.location?.verticalAccuracy ?? 0 > 0 {
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
.font(.callout)
.font(.footnote)
}
if locationHelper.locationManager.location?.courseAccuracy ?? 0 > 0 {
Label("Heading \(String(format: "%.2f", locationHelper.locationManager.location?.course ?? 0))°", systemImage: "location.circle")
.font(.callout)
.font(.footnote)
}
if locationHelper.locationManager.location?.speedAccuracy ?? 0 > 0 {
Label("Speed \(speed.formatted())", systemImage: "speedometer")
.font(.callout)
.font(.footnote)
}
Toggle(isOn: $provideLocation) {
Label("provide.location", systemImage: "location.circle.fill")
.font(.footnote)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
@ -74,10 +75,9 @@ struct AppSettings: View {
}
Text("phone.gps.interval.description")
.font(.caption)
.font(.caption2)
.foregroundColor(.gray)
}
}
TilesView()
}

View file

@ -45,7 +45,7 @@
"channel.utilization"="Channel Utilization";
"channels"="Channels";
"clear.app.data"="Clear App Data";
"clear.log"="Clear Log";
"clear.log"="Clear";
"close"="Close";
"config.save.confirm"="After config values save the node will reboot.";
"communicating"="Communicating with device. .";