import CocoaLumberjackSwift
import Foundation
import MapKit
// MARK: - ThreemaPOI
struct ThreemaPOI: Decodable {
let id: Int
let name: String
let lat: Double
let lon: Double
let dist: Int
let category: POICategory
let subcategory: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: CodingKeys.id)
self.name = try container.decode(String.self, forKey: CodingKeys.name)
self.lat = try container.decode(Double.self, forKey: CodingKeys.lat)
self.lon = try container.decode(Double.self, forKey: CodingKeys.lon)
self.dist = try container.decode(Int.self, forKey: CodingKeys.dist)
// The POIs category gets sent as key in JSON and changes for each received POI, therefore decoding is not
// straightforward
let receivedCategory = container.allKeys.first(where: { key in
POICategory.allCases.contains { $0.rawValue == key.stringValue }
}) ?? .other
self.category = POICategory.allCases.first(where: { receivedCategory.stringValue == $0.rawValue }) ?? .other
self.subcategory = try container.decodeIfPresent(String.self, forKey: receivedCategory) ?? ""
}
enum CodingKeys: String, CodingKey {
case id
case name
case lat
case lon
case dist
// Categories
case amenity
case tourism
case sport
case leisure
case shop
case natural
case publicTransport = "public_transport"
case aerialway
case aeroway
case highway
case place
case marked
case current
case other
}
}
// MARK: - PointOfInterest
final class PointOfInterest: Equatable, Hashable {
var type: POIType
var name: String
var location: CLLocation
var distance: POIDistance
var category: POICategory
var detailCategory: String
var address: String?
var image: UIImage
var updatedLocation: CLLocation
// MARK: - Lifecycle
init(receivedPOI: ThreemaPOI) {
self.type = .threemaPOI(id: receivedPOI.id)
self.name = receivedPOI.name
self.location = CLLocation(latitude: receivedPOI.lat, longitude: receivedPOI.lon)
self.updatedLocation = location
self.distance = .distance(receivedPOI.dist)
self.category = receivedPOI.category
self.detailCategory = receivedPOI.subcategory
self.image = PointOfInterest.icon(for: receivedPOI.subcategory)
}
init(
type: POIType,
name: String,
location: CLLocation,
distance: Int,
category: POICategory,
detailCategory: String
) {
self.type = type
self.name = name
self.location = location
self.updatedLocation = location
self.distance = .distance(distance)
self.category = category
self.detailCategory = detailCategory
self.image = PointOfInterest.icon(for: detailCategory)
}
init(
type: POIType,
name: String,
location: CLLocation,
distance: POIDistance,
category: POICategory,
detailCategory: String
) {
self.type = type
self.name = name
self.location = location
self.updatedLocation = location
self.distance = distance
self.category = category
self.detailCategory = detailCategory
self.image = PointOfInterest.icon(for: detailCategory)
}
// MARK: - Protocol Functions
static func == (lhs: PointOfInterest, rhs: PointOfInterest) -> Bool {
lhs.type == rhs.type
}
func hash(into hasher: inout Hasher) {
hasher.combine(type)
}
}
// MARK: - Annotation
final class POIAnnotation: MKPointAnnotation {
var type: POIType?
init(for poi: PointOfInterest) {
super.init()
self.type = poi.type
self.coordinate = poi.location.coordinate
self.title = poi.name
}
}
// MARK: - Enums
enum POIType: Hashable, Equatable {
case currentLocationPOI
case markedLocationPOI
case threemaPOI(id: Int)
}
enum POIDistance: Hashable, Equatable {
case notAvailable
case notSet
case distance(Int)
}
enum POICategory: String, CaseIterable {
// Given by Backend
case amenity
case tourism
case sport
case leisure
case shop
case natural
case publicTransport
case aerialway
case aeroway
case highway
case place
// Additional for Handling
case marked
case current
case other
}
extension PointOfInterest {
private static let symbolDictionary = [
// Own poi-subcategories
"current": "location.fill",
"marked": "mappin.and.ellipse",
// IOS 13
"department_store": "bag.fill",
"car": "car.fill",
"cinema": "film",
"conveniecne": "bag.fill",
"gallery": "photo.fill.on.rectangle.fill",
"gift": "gift.fill",
"hotel": "bed.double.fill",
"information": "info.circle.fill",
"mall": "bag.fill",
"optician": "eyeglasses",
"post_office": "envelope.fill",
"recylcing": "arrow.3.trianglepath",
"hairdresser": "scissors",
"station": "tram.fill",
"supermarket": "cart.fill",
// IOS 14
"atm": "banknote.fill",
"bank": "banknote.fill",
"beauty": "comb.fill",
"bicycle": "bicycle",
"books": "books.vertical.fill",
"bus_stop": "bus.fill",
"car_sharing": "car.2.fill",
"dentist": "cross.case.fill",
"doctors": "cross.case.fill",
"garden": "leaf.fill",
"library": "books.vertical.fill",
"mobile_phone": "simcard.2.fill",
"pharmacy": "pills.fill",
"playground": "die.face.5.fill",
"platform": "bus.fill",
"school": "graduationcap.fill",
"stop_position": "bus.fill",
"ticket": "ticket.fill",
"viewpoint": "binoculars.fill",
// IOS 15
"boat_renting": "ferry.fill",
"boat_sharing": "ferry.fill",
"cafe": "fork.knife",
"coffe": "fork.knife",
"clothes": "tshirt.fill",
"ferry_terminal": "ferry.fill",
"parking": "parkingsign",
"pub": "fork.knife",
"restaurant": "fork.knife",
"theatre": "theatermasks.fill",
"travel_agency": "globe.europe.africa.fill",
"fast_food": "takeoutbag.and.cup.and.straw.fill",
"fuel": "fuelpump.fill",
"yachting": "ferry.fill",
]
/// SF Symbol representing the subcategory, or pin image on iOS 12 or no image is set for subcategory
/// - Parameter subCategory: Subcategory of a POI
/// - Returns: SF Symbol for provided subcategory
static func icon(for subCategory: String) -> UIImage {
if let value = symbolDictionary[subCategory],
let image = UIImage(systemName: value) {
return image
}
return UIImage(systemName: "mappin")!
}
}
¤ Dauer der Verarbeitung: 0.0 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|