-
Notifications
You must be signed in to change notification settings - Fork 31
/
Overlay.swift
425 lines (348 loc) · 14.7 KB
/
Overlay.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#if os(OSX)
import Cocoa
#else
import UIKit
#endif
import Polyline
let allowedCharacterSet: CharacterSet = {
var characterSet = CharacterSet.urlPathAllowed
characterSet.remove(charactersIn: "/)")
return characterSet
}()
/**
A feature that can be drawn atop the map.
*/
@objc(MBOverlay)
public protocol Overlay: NSObjectProtocol {}
/**
A feature centered over a specific geographic coordinate.
*/
@objc(MBPoint)
public protocol Point: Overlay {
/// The geographic coordinate to place the point at.
var coordinate: CLLocationCoordinate2D { get }
}
/**
A pin-shaped marker image.
*/
@objc(MBMarkerImage)
open class MarkerImage: NSObject {
/**
The size of a marker.
*/
@objc(MBMarkerSize)
public enum Size: Int, CustomStringConvertible {
/**
A small marker.
A small marker in a snapshot is the same size as a medium-sized marker in a classic snapshot.
*/
case small
/**
A medium-sized marker.
A medium-sized marker in a snapshot is the same size as a large marker in a classic snapshot.
*/
case medium
/// A large marker.
case large
public var description: String {
switch self {
case .small:
return "s"
case .medium:
return "m"
case .large:
return "l"
}
}
}
/// Something simple that can be placed atop a marker.
public enum Label: CustomStringConvertible {
/// A lowercase English letter from A through Z. An uppercase letter is automatically converted to a lowercase letter.
case letter(Character)
/// A number from 0 through 99.
case number(Int)
/**
The name of a [Maki](https://www.mapbox.com/maki-icons/) icon.
The Static API uses Maki v4.0.0. See valid values at the [Maki](https://www.mapbox.com/maki-icons/) website. The classic Static API uses Maki v0.5.0. Valid values for classic snapshots are identified by the `icon` values in [this JSON file](https://github.com/mapbox/maki/blob/v0.5.0/_includes/maki.json).
*/
case iconName(String)
public var description: String {
switch self {
case .letter(let letter):
let lower = "\(letter)".lowercased()
assert(letter >= "a" && letter <= "z")
return lower
case .number(let number):
assert(number >= 0 && number < 100)
return "\(number)"
case .iconName(let name):
return "\(name)"
}
}
}
/**
The size of the marker.
By default, the marker is small.
*/
@objc open var size: Size
/**
A label or Maki icon to place atop the pin.
By default, the marker has no label.
*/
open var label: Label?
#if os(OSX)
/**
The color of the pin part of the marker.
By default, the marker is red.
*/
@objc open var color: NSColor = .red
#else
/**
The color of the pin part of the marker.
By default, the marker is red.
*/
@objc open var color: UIColor = .red
#endif
/**
Initializes a red marker image with the given options.
- parameter size: The size of the marker.
- parameter label: A label or Maki icon to place atop the pin.
*/
internal init(size: Size, label: Label?) {
self.size = size
self.label = label
}
}
/**
A pin-shaped marker placed at a specific point on the map.
*/
@objc(MBMarker)
open class Marker: MarkerImage, Point {
/// The geographic coordinate to place the marker at.
@objc open var coordinate: CLLocationCoordinate2D
/**
Initializes a red marker with the given options.
- parameter coordinate: The geographic coordinate to place the marker at.
- parameter size: The size of the marker.
- parameter label: A label or Maki icon to place atop the pin.
*/
fileprivate init(coordinate: CLLocationCoordinate2D,
size: Size = .small,
label: Label?) {
self.coordinate = coordinate
super.init(size: size, label: label)
}
/**
Initializes a red marker labeled with an English letter.
- parameter coordinate: The geographic coordinate to place the marker at.
- parameter size: The size of the marker.
- parameter letter: An English letter from A through Z to place atop the pin.
*/
@objc public convenience init(coordinate: CLLocationCoordinate2D,
size: Size = .small,
letter: UniChar) {
self.init(coordinate: coordinate, size: size, label: .letter(Character(UnicodeScalar(letter)!)))
}
/**
Initializes a red marker labeled with a one- or two-digit number.
- parameter coordinate: The geographic coordinate to place the marker at.
- parameter size: The size of the marker.
- parameter number: A number from 0 through 99 to place atop the pin.
*/
@objc public convenience init(coordinate: CLLocationCoordinate2D,
size: Size = .small,
number: Int) {
self.init(coordinate: coordinate, size: size, label: .number(number))
}
/**
Initializes a red marker with a [Maki](https://www.mapbox.com/maki-icons/) icon.
The Maki icon set is [open source](https://github.com/mapbox/maki/) and [dedicated to the public domain](https://creativecommons.org/publicdomain/zero/1.0/).
The Static API uses Maki v4.0.0. See valid values at the [Maki](https://www.mapbox.com/maki-icons/) website. The classic Static API uses Maki v0.5.0. Valid values for classic snapshots are identified by the `icon` values in [this JSON file](https://github.com/mapbox/maki/blob/v0.5.0/_includes/maki.json).
- parameter coordinate: The geographic coordinate to place the marker at.
- parameter size: The size of the marker.
- parameter iconName: The name of a [Maki](https://www.mapbox.com/maki-icons/) icon to place atop the pin.
*/
@objc public convenience init(coordinate: CLLocationCoordinate2D,
size: Size = .small,
iconName: String) {
self.init(coordinate: coordinate, size: size, label: .iconName(iconName))
}
@objc open override var description: String {
let labelComponent: String
if let label = label {
labelComponent = "-\(label)"
} else {
labelComponent = ""
}
return "pin-\(size)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))"
}
}
/**
A custom, online image placed at a specific point on the map.
The marker image is always centered on the specified location. When creating an asymmetric marker like a pin, make sure that the tip of the pin is at the center of the image.
*/
@objc(MBCustomMarker)
open class CustomMarker: NSObject, Overlay {
/// The geographic coordinate to place the marker at.
open var coordinate: CLLocationCoordinate2D
/**
The HTTP or HTTPS URL of the image.
The API caches custom marker images according to the `Expires` and `Cache-Control` headers. If you host the image on your own server, make sure that at least one of these headers is set to an proper value to prevent repeated requests for the image.
*/
@objc open var url: URL
/**
Initializes a marker with the given coordinate and image URL.
- parameter coordinate: The geographic coordinate to place the marker at.
- parameter url: The HTTP or HTTPS URL of the image.
*/
@objc public init(coordinate: CLLocationCoordinate2D, url: URL) {
self.coordinate = coordinate
self.url = url
}
@objc open override var description: String {
let escapedURL = url.absoluteString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)!
return "url-\(escapedURL)(\(coordinate.longitude),\(coordinate.latitude))"
}
}
/**
A geographic object in [GeoJSON](https://www.mapbox.com/help/define-geojson/) format.
GeoJSON features may be styled according to the [simplestyle specification](https://github.com/mapbox/simplestyle-spec).
*/
@objc(MBGeoJSON)
open class GeoJSON: NSObject, Overlay {
/// String representation of the GeoJSON object to display.
@objc open var objectString: String
@objc open override var description: String {
let escapedObjectString = objectString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)!
return "geojson(\(escapedObjectString))"
}
/**
Initializes a [GeoJSON](https://www.mapbox.com/help/define-geojson/) overlay with the given GeoJSON object.
- parameter object: A valid GeoJSON object.
- returns: A GeoJSON overlay, or `nil` if the given object is not a valid JSON object. This initializer does not check whether the object is valid GeoJSON, but invalid GeoJSON will cause the request to fail.
*/
@objc public init(object: [String: Any]) throws {
let data = try JSONSerialization.data(withJSONObject: object, options: .sortedIfAvailable)
objectString = String(data: data, encoding: .utf8)!
}
/**
Initializes a [GeoJSON](https://www.mapbox.com/help/define-geojson/) overlay with the given string representation of a GeoJSON object.
This initializer does not check whether the object is valid JSON or GeoJSON, but invalid JSON or GeoJSON will cause the request to fail. To perform basic JSON validation (but not GeoJSON validation), use the `init(object:)` initializer.
- parameter objectString: The string representation of a valid GeoJSON object.
*/
@objc public init(objectString: String) {
self.objectString = objectString
}
}
/**
A polyline or polygon placed along a path atop the map.
*/
@objc(MBPath)
open class Path: NSObject, Overlay {
/**
An array of geographic coordinates defining the path of the overlay.
*/
@objc open var coordinates: [CLLocationCoordinate2D]
/**
The stroke width of the overlay, measured in points.
By default, the overlay is 1 point wide.
*/
@objc open var strokeWidth: Int = 1
#if os(OSX)
/**
The stroke color of the overlay.
By default, the overlay is stroked with Davy’s gray (33% white).
*/
@objc open var strokeColor = NSColor(hexString: "555")
/**
The fill color of the overlay.
By default, the overlay is filled with Davy’s gray (33% white).
*/
@objc open var fillColor = NSColor(hexString: "555")
#else
/**
The stroke color of the overlay.
By default, the overlay is stroked with Davy’s gray (33% white).
*/
@objc open var strokeColor = UIColor(hexString: "555")
/**
The fill color of the overlay.
By default, the overlay is filled with Davy’s gray (33% white).
*/
@objc open var fillColor = UIColor(hexString: "555")
#endif
/**
Initializes a polyline overlay with the given vertices.
The polyline is 1 point wide and stroked with Davy’s gray (33% white).
To turn the overlay into a polygon, close the path by ensuring that the first and last coordinates are the same. To fill the polygon, set the `fillColor` property to a color whose alpha component is greater than 0.0.
- parameter coordinates: An array of geographic coordinates defining the path of the overlay.
*/
@objc public init(coordinates: [CLLocationCoordinate2D]) {
self.coordinates = coordinates
}
/**
Initializes a polyline overlay with the given vertices, stored in a C array.
The polyline is 1 point wide and stroked with Davy’s gray (33% white).
To turn the overlay into a polygon, close the path by ensuring that the first and last coordinates are the same. To fill the polygon, set the `fillColor` property to a color whose alpha component is greater than 0.0.
- parameter coordinates: An array of geographic coordinates defining the path of the overlay.
- note: This initializer is intended for Objective-C usage. In Swift code, use the `init(coordinates:)` initializer.
*/
@objc public init(coordinates: UnsafePointer<CLLocationCoordinate2D>, count: UInt) {
var convertedCoordinates: [CLLocationCoordinate2D] = []
for i in 0..<count {
convertedCoordinates.append(coordinates.advanced(by: Int(i)).pointee)
}
self.coordinates = convertedCoordinates
}
/**
The number of vertices.
- note: This initializer is intended for Objective-C usage. In Swift code, use the `coordinates.count` property.
*/
@objc open var coordinateCount: UInt {
return UInt(coordinates.count)
}
/**
Retrieves the vertices.
- parameter coordinates: A pointer to a C array of `CLLocationCoordinate2D` instances. On output, this array contains all the vertices of the overlay.
- precondition: `coordinates` must be large enough to hold `coordinateCount` instances of `CLLocationCoordinate2D`.
- note: This initializer is intended for Objective-C usage. In Swift code, use the `coordinates` property.
*/
@objc open func getCoordinates(_ coordinates: UnsafeMutablePointer<CLLocationCoordinate2D>) {
for i in 0..<self.coordinates.count {
coordinates.advanced(by: i).pointee = self.coordinates[i]
}
}
@objc open override var description: String {
let encodedPolyline = encodeCoordinates(coordinates).addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)!
var description = "path-\(strokeWidth)+\(strokeColor.toHexString())-\(strokeColor.alphaComponent)"
if fillColor.alphaComponent > 0 {
description += "+\(fillColor.toHexString())-\(fillColor.alphaComponent)"
}
description += "(\(encodedPolyline))"
return description
}
}
extension JSONSerialization.WritingOptions {
static var sortedIfAvailable: JSONSerialization.WritingOptions {
#if DEBUG
#if os(OSX)
if #available(OSX 10.13, *) {
return [.sortedKeys]
}
#elseif os(iOS)
if #available(iOS 11.0, *) {
return [.sortedKeys]
}
#elseif os(tvOS)
if #available(tvOS 11.0, *) {
return [.sortedKeys]
}
#elseif os(watchOS)
if #available(watchOS 4.0, *) {
return [.sortedKeys]
}
#endif
#endif
return []
}
}