From d635e2363e583d506847c0ba76521f3efded51b5 Mon Sep 17 00:00:00 2001 From: Ian Wagner Date: Sun, 29 Sep 2024 18:05:37 +0900 Subject: [PATCH] Allow filtering to a set of layers --- .../AutocompleteSearch.swift | 36 ++++++++++++++----- .../Extensions.swift | 17 +++++++++ .../SearchResult.swift | 4 +-- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Sources/StadiaMapsAutocompleteSearch/AutocompleteSearch.swift b/Sources/StadiaMapsAutocompleteSearch/AutocompleteSearch.swift index 48d7f17..1fc4bf0 100644 --- a/Sources/StadiaMapsAutocompleteSearch/AutocompleteSearch.swift +++ b/Sources/StadiaMapsAutocompleteSearch/AutocompleteSearch.swift @@ -9,19 +9,23 @@ public struct AutocompleteSearch: View { @State private var isLoading = false let userLocation: CLLocation? + let limitLayers: [PeliasLayer]? let onResultSelected: ((PeliasGeoJSONFeature) -> Void)? @ViewBuilder let resultViewBuilder: (PeliasGeoJSONFeature, CLLocation?) -> T - /// Creates an autocomplete geographic search view. + /// Creates an search view with text input + /// and a result list that updates as the user types. /// - Parameters: - /// - apiKey: Your Stadia Maps API key - /// - useEUEndpoint: Send requests to servers located in the European Union (may significantly degrade performance outside Europe) - /// - userLocation: If present, biases the search for results near a specific location and displays results with (straight-line) distances from this location - /// - onResultSelected: An optional callback invoked when a result is tapped in the list - /// - resultViewBuilder: An optional result view builder which lets you replace the default list element view (``SearchResult``) with your own + /// - apiKey: Your [Stadia Maps API key](https://docs.stadiamaps.com/authentication/). + /// - useEUEndpoint: Send requests to servers located in the European Union. Note that this may significantly degrade performance for users outside Europe. + /// - userLocation: If present, biases the search for results near a specific location. Additionally, results using the default (``SearchResult``) view will display the straight-line distances from this location. + /// - limitLayers: Optionally limits the searched layers to the specified set. + /// - onResultSelected: An optional callback invoked when the user taps on a result in the list. This allows you to build interactivity, such as launching navigation or flying to a location on a map. + /// - resultViewBuilder: An optional result view builder which lets you replace the default list element view (``SearchResult``) with your own. public init(apiKey: String, useEUEndpoint: Bool = false, userLocation: CLLocation? = nil, + limitLayers: [PeliasLayer]? = nil, onResultSelected: ((PeliasGeoJSONFeature) -> Void)? = nil, @ViewBuilder resultViewBuilder: @escaping (PeliasGeoJSONFeature, CLLocation?) -> T = { feature, userLocation in SearchResult(feature: feature, relativeTo: userLocation) @@ -32,6 +36,7 @@ public struct AutocompleteSearch: View { StadiaMapsAPI.basePath = "https://api-eu.stadiamaps.com" } self.userLocation = userLocation + self.limitLayers = limitLayers self.onResultSelected = onResultSelected self.resultViewBuilder = resultViewBuilder } @@ -79,14 +84,14 @@ public struct AutocompleteSearch: View { let result: PeliasResponse if autocomplete { - result = try await GeocodingAPI.autocomplete(text: query, focusPointLat: userLocation?.coordinate.latitude, focusPointLon: userLocation?.coordinate.longitude) + result = try await GeocodingAPI.autocomplete(text: query, focusPointLat: userLocation?.coordinate.latitude, focusPointLon: userLocation?.coordinate.longitude, layers: limitLayers) } else { - result = try await GeocodingAPI.search(text: query, focusPointLat: userLocation?.coordinate.latitude, focusPointLon: userLocation?.coordinate.longitude) + result = try await GeocodingAPI.search(text: query, focusPointLat: userLocation?.coordinate.latitude, focusPointLon: userLocation?.coordinate.longitude, layers: limitLayers) } // Only replace results if the text matches the current input if query == searchText { - searchResults = result.features + searchResults = result.features.filter({ $0.center != nil }) } } @@ -113,6 +118,19 @@ private let previewApiKey = "YOUR-API-KEY" } } +// This shows how to limit the search layers. +// The coarse meta-layer allows for quicker lookups, +// by excluding the address and venue layers. +#Preview("Coarse Lookup") { + if previewApiKey == "YOUR-API-KEY" { + Text("You need an API key for this to be very useful. Get one at client.stadiamaps.com.") + } else { + AutocompleteSearch(apiKey: previewApiKey, limitLayers: [.coarse]) { selection in + print("Selected: \(selection)") + } + } +} + #Preview("Custom Result View") { if previewApiKey == "YOUR-API-KEY" { Text("You need an API key for this to be very useful. Get one at client.stadiamaps.com.") diff --git a/Sources/StadiaMapsAutocompleteSearch/Extensions.swift b/Sources/StadiaMapsAutocompleteSearch/Extensions.swift index 13efdc6..e29316e 100644 --- a/Sources/StadiaMapsAutocompleteSearch/Extensions.swift +++ b/Sources/StadiaMapsAutocompleteSearch/Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import CoreLocation import StadiaMaps import SwiftUI @@ -30,6 +31,22 @@ public extension PeliasGeoJSONFeature { return components.compactMap({ $0 }).joined(separator: ", ") } + + /// The approximate center of the feature. + /// + /// Note that the API does not currently include any more info than a bounding box for non-point features. + /// We just compute the mathematical middle for now. + var center: CLLocation? { + if (geometry.type == .point) { + return CLLocation(latitude: geometry.coordinates[1], longitude: geometry.coordinates[0]) + } else if let bbox { + let lat = (bbox[1] + bbox[3]) / 2 + let lon = (bbox[0] + bbox[2]) / 2 + return CLLocation(latitude: lat, longitude: lon) + } else { + return nil + } + } } extension PeliasLayer { diff --git a/Sources/StadiaMapsAutocompleteSearch/SearchResult.swift b/Sources/StadiaMapsAutocompleteSearch/SearchResult.swift index e72fe51..820ddc6 100644 --- a/Sources/StadiaMapsAutocompleteSearch/SearchResult.swift +++ b/Sources/StadiaMapsAutocompleteSearch/SearchResult.swift @@ -35,8 +35,8 @@ public struct SearchResult: View { Text(feature.subtitle) .font(.caption) } - if let relativeTo { - let distance = relativeTo.distance(from: CLLocation(latitude: feature.geometry.coordinates[1], longitude: feature.geometry.coordinates[0])) + if let relativeTo, let center = feature.center { + let distance = relativeTo.distance(from: center) Text(formatter.string(fromDistance: distance)) .font(.caption) .frame(maxWidth: .infinity, alignment: .trailing)