Skip to content

Commit

Permalink
Allow filtering to a set of layers
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthetechie committed Sep 29, 2024
1 parent e3b053a commit d635e23
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 11 deletions.
36 changes: 27 additions & 9 deletions Sources/StadiaMapsAutocompleteSearch/AutocompleteSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ public struct AutocompleteSearch<T: View>: 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)
Expand All @@ -32,6 +36,7 @@ public struct AutocompleteSearch<T: View>: View {
StadiaMapsAPI.basePath = "https://api-eu.stadiamaps.com"
}
self.userLocation = userLocation
self.limitLayers = limitLayers
self.onResultSelected = onResultSelected
self.resultViewBuilder = resultViewBuilder
}
Expand Down Expand Up @@ -79,14 +84,14 @@ public struct AutocompleteSearch<T: View>: 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 })
}
}

Expand All @@ -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.")
Expand Down
17 changes: 17 additions & 0 deletions Sources/StadiaMapsAutocompleteSearch/Extensions.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import CoreLocation
import StadiaMaps
import SwiftUI

Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions Sources/StadiaMapsAutocompleteSearch/SearchResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit d635e23

Please sign in to comment.