ProMotion-mapbox 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +376 -0
- data/lib/ProMotion-mapbox.rb +20 -0
- data/lib/ProMotion/map/map_screen.rb +6 -0
- data/lib/ProMotion/map/map_screen_annotation.rb +74 -0
- data/lib/ProMotion/map/map_screen_module.rb +448 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a6c7b1a09e8296b378386a2508ff9803ffee0a3
|
4
|
+
data.tar.gz: 4f9f1e7093bb2981aeffe516cb783f033cfab280
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 16b2612226da961d263dc65d83f1f060238cb18d646c9fcd992961d5516534f13ab7e3c549569c23b469777ebf7bc22587835077f55eea0776bbbe43dcf11af2
|
7
|
+
data.tar.gz: ab5d5c5075abb292a91a3fb85650136fd9c2893fc017f9dfb2094120fedfe16e5a1fe95aa1d1ee6663d22fe361dc386980db74c6faf087a96f859ef06385e0c7
|
data/README.md
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
# ProMotion-mapbox
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/ProMotion-mapbox)
|
4
|
+
|
5
|
+
ProMotion-mapbox provides a PM::MapScreen, forked from the
|
6
|
+
popular RubyMotion gem [ProMotion-map](https://github.com/clearsightstudio/ProMotion-map).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'ProMotion-mapbox'
|
12
|
+
```
|
13
|
+
```ruby
|
14
|
+
rake pod:install
|
15
|
+
```
|
16
|
+
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Easily create a map screen, complete with annotations.
|
21
|
+
|
22
|
+
*Has all the methods of PM::Screen*
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class MyMapScreen < PM::MapScreen
|
26
|
+
mapbox_setup access_token: "YOU_MAPBOX_ACCESS_TOKEN",
|
27
|
+
tile_source: "mylogin.map"
|
28
|
+
|
29
|
+
title "My Map"
|
30
|
+
start_position latitude: 35.090648651123, longitude: -82.965972900391, radius: 4
|
31
|
+
tap_to_add
|
32
|
+
|
33
|
+
def annotation_data
|
34
|
+
[{
|
35
|
+
longitude: -82.965972900391,
|
36
|
+
latitude: 35.090648651123,
|
37
|
+
title: "Rainbow Falls",
|
38
|
+
subtitle: "Nantahala National Forest",
|
39
|
+
action: :show_forest,
|
40
|
+
pin_color: :green
|
41
|
+
},{
|
42
|
+
longitude: -82.966093558105,
|
43
|
+
latitude: 35.092520895652,
|
44
|
+
title: "Turtleback Falls",
|
45
|
+
subtitle: "Nantahala National Forest",
|
46
|
+
action: :show_forest,
|
47
|
+
pin_color: :purple]
|
48
|
+
},{
|
49
|
+
longitude: -82.95916,
|
50
|
+
latitude: 35.07496,
|
51
|
+
title: "Windy Falls",
|
52
|
+
action: :show_forest
|
53
|
+
},{
|
54
|
+
longitude: -82.943031505056,
|
55
|
+
latitude: 35.102516828489,
|
56
|
+
title: "Upper Bearwallow Falls",
|
57
|
+
subtitle: "Gorges State Park",
|
58
|
+
action: :show_forest
|
59
|
+
},{
|
60
|
+
longitude: -82.956244328014,
|
61
|
+
latitude: 35.085548421623,
|
62
|
+
title: "Stairway Falls",
|
63
|
+
subtitle: "Gorges State Park",
|
64
|
+
your_param: "CustomWhatever",
|
65
|
+
action: :show_forest
|
66
|
+
}, {
|
67
|
+
coordinate: CLLocationCoordinate2DMake(35.090648651123, -82.965972900391)
|
68
|
+
title: "Rainbow Falls",
|
69
|
+
subtitle: "Nantahala National Forest",
|
70
|
+
image: UIImage.imageNamed("custom-pin"),
|
71
|
+
action: :show_forest
|
72
|
+
}]
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_forest
|
76
|
+
selected = selected_annotations.first
|
77
|
+
# Do something with the selected annotation.
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Here's a neat way to zoom into a specific marker in an animated fashion and then select the marker:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
def zoom_to_marker(marker)
|
86
|
+
set_region region(coordinate: marker.coordinate, radius: 5) # Radius are specified in nautical miles.
|
87
|
+
select_annotation marker
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
---
|
92
|
+
|
93
|
+
### Methods
|
94
|
+
|
95
|
+
#### annotation_data
|
96
|
+
|
97
|
+
Method that is called to get the map's annotation data and build the map. If you do not want any annotations, simply return an empty array.
|
98
|
+
|
99
|
+
All possible properties:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
{
|
103
|
+
# REQUIRED -or- use :coordinate
|
104
|
+
longitude: -82.956244328014,
|
105
|
+
latitude: 35.085548421623,
|
106
|
+
|
107
|
+
# REQUIRED -or- use :longitude & :latitude
|
108
|
+
coordinate: CLLocationCoordinate2DMake(35.085548421623, -82.956244328014)
|
109
|
+
|
110
|
+
title: "Stairway Falls", # REQUIRED
|
111
|
+
subtitle: "Gorges State Park",
|
112
|
+
image: "my_custom_image",
|
113
|
+
pin_color: :red, # Defaults to :red. Other options are :green or :purple or any UIColor
|
114
|
+
left_accessory: my_button,
|
115
|
+
right_accessory: my_other_button,
|
116
|
+
action: :my_action, # Overrides :right_accessory
|
117
|
+
action_button_type: UIButtonTypeContactAdd # Defaults to UIButtonTypeDetailDisclosure
|
118
|
+
}
|
119
|
+
```
|
120
|
+
|
121
|
+
You may pass whatever properties you want in the annotation hash, but (`:longitude` && `:latitude` || `:coordinate`), and `:title` are required.
|
122
|
+
|
123
|
+
Use `:image` to specify a custom image. Pass in a string to conserve memory and it will be converted using `UIImage.imageNamed(your_string)`. If you pass in a `UIImage`, we'll use that, but keep in mind that there will be another unnecessary copy of the UIImage in memory.
|
124
|
+
|
125
|
+
Use `:left_accessory` and `:right_accessory` to specify a custom accessory, like a button.
|
126
|
+
|
127
|
+
You can access annotation data you've arbitrarily stored in the hash by calling `annotation_instance.params[:your_param]`.
|
128
|
+
|
129
|
+
The `:action` parameter specifies a method that should be run when the detail button is tapped on the annotation. It automatically adds a `UIButtonTypeDetailDisclosure` button to the `:left_accessory`. In your method you can find out which annotation's accessory was tapped by calling `selected_annotations.first`.
|
130
|
+
|
131
|
+
#### update_annotation_data
|
132
|
+
|
133
|
+
Forces a reload of all the annotations
|
134
|
+
|
135
|
+
#### annotations
|
136
|
+
|
137
|
+
Returns an array of all the annotations.
|
138
|
+
|
139
|
+
#### center
|
140
|
+
|
141
|
+
Returns a `CLLocation2D` instance with the center coordinates of the map.
|
142
|
+
|
143
|
+
#### center=({latitude: Float, longitude: Float, animated: Boolean})
|
144
|
+
|
145
|
+
Sets the center of the map. `animated` property defaults to `true`.
|
146
|
+
|
147
|
+
#### show_user_location
|
148
|
+
|
149
|
+
Shows the user's location on the map. Must be called in the view initialization sequence on `will_appear` or _after_.
|
150
|
+
|
151
|
+
#### look_up_location(CLLocation) { |placemark, error| }
|
152
|
+
|
153
|
+
This method takes a CLLocation object and will return one to many CLPlacemark to represent nearby data.
|
154
|
+
|
155
|
+
##### iOS 8 Location Requirements
|
156
|
+
|
157
|
+
iOS 8 introduced stricter location services requirements. You are now required to add a few key/value pairs to the `Info.plist`. Add these two lines to your `Rakefile` (with your descriptions, obviously):
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
app.info_plist['NSLocationAlwaysUsageDescription'] = 'Description'
|
161
|
+
app.info_plist['NSLocationWhenInUseUsageDescription'] = 'Description'
|
162
|
+
```
|
163
|
+
|
164
|
+
*Note: you need both keys to use `get_once`, so it's probably best to just include both no matter what.* See [Apple's documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW18) on iOS 8 location services requirements for more information.
|
165
|
+
|
166
|
+
#### hide_user_location
|
167
|
+
|
168
|
+
Hides the user's location on the map.
|
169
|
+
|
170
|
+
#### showing_user_location?
|
171
|
+
|
172
|
+
Returns a `Boolean` of whether or not the map view is currently showing the user's location.
|
173
|
+
|
174
|
+
#### user_location
|
175
|
+
|
176
|
+
Returns a `CLLocation2D` object of the user's location or `nil` if the user location is not being tracked
|
177
|
+
|
178
|
+
#### zoom_to_user(radius = 0.05, animated=true)
|
179
|
+
|
180
|
+
Zooms to the user's location. If the user's location is not currently being shown on the map, it will show it first. `radius` is the distance in nautical miles from the center point (user location) to the corners of a virtual bounding box.
|
181
|
+
|
182
|
+
#### select_annotation(annotation, animated=true)
|
183
|
+
|
184
|
+
Selects a single annotation.
|
185
|
+
|
186
|
+
#### select_annotation_at(annotation_index, animated=true)
|
187
|
+
|
188
|
+
Selects a single annotation using the annotation at the index of your `annotation_data` array.
|
189
|
+
|
190
|
+
#### selected_annotation
|
191
|
+
|
192
|
+
Returns the annotation that is selected. If no annotation is selected, returns `nil`.
|
193
|
+
|
194
|
+
#### deselect_annotation(animated=false)
|
195
|
+
|
196
|
+
Deselects any selected annotation.
|
197
|
+
|
198
|
+
#### add_annotation(annotation)
|
199
|
+
|
200
|
+
Adds a new annotation to the map. Refer to `annotation_data` (above) for hash properties.
|
201
|
+
|
202
|
+
#### add_annotations(annotations)
|
203
|
+
|
204
|
+
Adds more than one annotation at a time to the map.
|
205
|
+
|
206
|
+
#### clear_annotations
|
207
|
+
|
208
|
+
Removes all annotations from the `MapScreen`.
|
209
|
+
|
210
|
+
#### zoom_to_fit_annotations({animated:true, include_user:false})
|
211
|
+
|
212
|
+
Changes the zoom and center point of the `MapScreen` to fit all the annotations. Passing `include_user` as `true` will cause the zoom to not only include the annotations from `annotation_data` but also the user pin in the zoom region calculation.
|
213
|
+
|
214
|
+
#### set_region(region, animated=true)
|
215
|
+
|
216
|
+
Sets the region of the `MapScreen`. `region` should be an instance of `MKCoordinateRegion`.
|
217
|
+
|
218
|
+
#### region(center_location,radius=10)
|
219
|
+
|
220
|
+
Mapbox API doesn't have the concept of a region. Instead, we can zoom to a virtual bounding box defined by its Sourthwest and Northeast
|
221
|
+
corners.
|
222
|
+
The ```region``` methods takes a ```center_location``` and a radius. The distance from the center to the corners (and thus the zoom level) will be the ```radius``` times 1820 meters (1 Nautical mile)
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
my_region = region({
|
226
|
+
CLLocationCoordinate2D.new(35.0906,-82.965),
|
227
|
+
radius: 11
|
228
|
+
})
|
229
|
+
```
|
230
|
+
|
231
|
+
---
|
232
|
+
|
233
|
+
### Class Methods
|
234
|
+
|
235
|
+
#### start_position(latitude: Float, longitude: Float, radius: Float)
|
236
|
+
|
237
|
+
Class method to set the initial starting position of the `MapScreen`.
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
class MyMapScreen < PM::MapScreen
|
241
|
+
start_position latitude: 36.10, longitude: -80.26, radius: 4
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
`radius` is the zoom level of the map in miles (default: 10).
|
246
|
+
|
247
|
+
#### tap_to_add(length: Float, target: Object, action: Selector, annotation: Hash)
|
248
|
+
|
249
|
+
Lets a user long press the map to drop an annotation where they pressed.
|
250
|
+
|
251
|
+
##### Default values:
|
252
|
+
|
253
|
+
You can override any of these values. The `annotation` parameter can take any options specified in the annotation documentation above except `:latitude`, `:longitude`, and `:coordinate`.
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
length: 2.0,
|
257
|
+
target: self,
|
258
|
+
action: "gesture_drop_pin:",
|
259
|
+
annotation: {
|
260
|
+
title: "Dropped Pin",
|
261
|
+
animates_drop: true
|
262
|
+
}
|
263
|
+
```
|
264
|
+
|
265
|
+
##### Notifications
|
266
|
+
|
267
|
+
This feature posts two different `NSNotificationCenter` notifications:
|
268
|
+
|
269
|
+
**ProMotionMapWillAddPin:** Fired the moment the long press gesture is recognized, before the pin is added.
|
270
|
+
|
271
|
+
**ProMotionMapAddedPin:** Fired after the pin has been added to the map.
|
272
|
+
|
273
|
+
##### Example:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
# Simple Example
|
277
|
+
class MyMapScreen < PM::MapScreen
|
278
|
+
title "My Map Screen"
|
279
|
+
tap_to_add length: 1.5
|
280
|
+
def annotations
|
281
|
+
[]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
# A More Complex Example
|
288
|
+
class MyMapScreen < PM::MapScreen
|
289
|
+
title "My Map Screen"
|
290
|
+
tap_to_add length: 1.5, annotation: {animates_drop: true, title: "A Cool New Pin"}
|
291
|
+
def annotations
|
292
|
+
[]
|
293
|
+
end
|
294
|
+
|
295
|
+
def will_appear
|
296
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector:"pin_adding:") , name:"ProMotionMapWillAddPin", object:nil)
|
297
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector:"pin_added:") , name:"ProMotionMapAddedPin", object:nil)
|
298
|
+
end
|
299
|
+
|
300
|
+
def will_disappear
|
301
|
+
NSNotificationCenter.defaultCenter.removeObserver(self)
|
302
|
+
end
|
303
|
+
|
304
|
+
def pin_adding(notification)
|
305
|
+
# We only want one pin on the map at a time
|
306
|
+
clear_annotations
|
307
|
+
end
|
308
|
+
|
309
|
+
def pin_added(notification)
|
310
|
+
# Once the pin is dropped we want to select it
|
311
|
+
select_annotation_at(0)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
```
|
315
|
+
|
316
|
+
---
|
317
|
+
|
318
|
+
### Delegate callbacks
|
319
|
+
|
320
|
+
These methods (if implemented in your `MapScreen`) will be called when the corresponding `MKMapViewDelegate` method is invoked:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
def will_change_region(animated)
|
324
|
+
# Do something when the region will change
|
325
|
+
# The animated parameter is optional so you can also define it is simply:
|
326
|
+
# def will_change_region
|
327
|
+
# end
|
328
|
+
end
|
329
|
+
|
330
|
+
def on_change_region(animated)
|
331
|
+
# Do something when the region changed
|
332
|
+
# The animated parameter is optional so you can also define it is simply:
|
333
|
+
# def on_change_region
|
334
|
+
# end
|
335
|
+
end
|
336
|
+
```
|
337
|
+
|
338
|
+
---
|
339
|
+
|
340
|
+
### CocoaTouch Property Convenience Methods
|
341
|
+
|
342
|
+
`MKMapView` contains multiple property setters and getters that can be accessed in a more ruby-like syntax:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
type # Returns a MKMapType
|
346
|
+
type = (MKMapType)new_type
|
347
|
+
|
348
|
+
zoom_enabled?
|
349
|
+
zoom_enabled = (bool)enabled
|
350
|
+
|
351
|
+
scroll_enabled?
|
352
|
+
scroll_enabled = (bool)enabled
|
353
|
+
|
354
|
+
pitch_enabled?
|
355
|
+
pitch_enabled = (bool)enabled
|
356
|
+
|
357
|
+
rotate_enabled?
|
358
|
+
rotate_enabled = (bool)enabled
|
359
|
+
```
|
360
|
+
|
361
|
+
---
|
362
|
+
|
363
|
+
### Accessors
|
364
|
+
|
365
|
+
#### `map` or `mapview`
|
366
|
+
|
367
|
+
Reference to the created RMMapView.
|
368
|
+
|
369
|
+
## Contributing
|
370
|
+
|
371
|
+
1. Fork it
|
372
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
373
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
374
|
+
4. Make some specs pass
|
375
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
376
|
+
6. Create new Pull Request
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
unless defined?(Motion::Project::Config)
|
3
|
+
raise "ProMotion-mapbox must be required within a RubyMotion project."
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'motion-cocoapods'
|
7
|
+
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
10
|
+
app.files << File.join(lib_dir_path, "ProMotion/map/map_screen_annotation.rb")
|
11
|
+
app.files << File.join(lib_dir_path, "ProMotion/map/map_screen_module.rb")
|
12
|
+
app.files << File.join(lib_dir_path, "ProMotion/map/map_screen.rb")
|
13
|
+
|
14
|
+
app.frameworks += %w(CoreLocation)
|
15
|
+
|
16
|
+
app.pods do
|
17
|
+
pod "Mapbox-iOS-SDK"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module ProMotion
|
2
|
+
class MapScreenAnnotation < RMAnnotation
|
3
|
+
attr_reader :params
|
4
|
+
|
5
|
+
def initialize(params = {},map_view)
|
6
|
+
@params = params
|
7
|
+
@map_view = map_view
|
8
|
+
set_defaults
|
9
|
+
|
10
|
+
if @params[:coordinate]
|
11
|
+
@params[:latitude] = @params[:coordinate].latitude
|
12
|
+
@params[:longitude] = @params[:coordinate].longitude
|
13
|
+
@coordinate = @params[:coordinate]
|
14
|
+
initWithMapView(map_view, coordinate: @coordinate, andTitle: @params[:title])
|
15
|
+
elsif @params[:latitude] && @params[:longitude]
|
16
|
+
@coordinate = CLLocationCoordinate2D.new(@params[:latitude], @params[:longitude])
|
17
|
+
initWithMapView(map_view, coordinate: @coordinate, andTitle: @params[:title])
|
18
|
+
else
|
19
|
+
PM.logger.error("You are required to specify :latitude and :longitude or :coordinate for annotations.")
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_defaults
|
25
|
+
@params = {
|
26
|
+
title: "Title",
|
27
|
+
pin_color: :red,
|
28
|
+
identifier: "Annotation-#{@params[:pin_color]}-#{@params[:image]}",
|
29
|
+
show_callout: true,
|
30
|
+
animates_drop: false,
|
31
|
+
maki_icon: nil,
|
32
|
+
}.merge(@params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def title
|
36
|
+
@params[:title]
|
37
|
+
end
|
38
|
+
|
39
|
+
def subtitle
|
40
|
+
@params[:subtitle] ||= nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def coordinate
|
44
|
+
@coordinate
|
45
|
+
end
|
46
|
+
|
47
|
+
def pin_color
|
48
|
+
@params[:pin_color]
|
49
|
+
end
|
50
|
+
|
51
|
+
def cllocation
|
52
|
+
CLLocation.alloc.initWithLatitude(@params[:latitude], longitude:@params[:longitude])
|
53
|
+
end
|
54
|
+
|
55
|
+
def setCoordinate(new_coordinate)
|
56
|
+
super
|
57
|
+
if new_coordinate.is_a? Hash
|
58
|
+
@coordinate = CLLocationCoordinate2D.new(new_coordinate[:latitude], new_coordinate[:longitude])
|
59
|
+
else
|
60
|
+
@coordinate = new_coordinate
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(meth, *args)
|
65
|
+
if @params[meth.to_sym]
|
66
|
+
@params[meth.to_sym]
|
67
|
+
else
|
68
|
+
PM.logger.warn "The annotation parameter \"#{meth}\" does not exist on this pin."
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,448 @@
|
|
1
|
+
module ProMotion
|
2
|
+
module MapScreenModule
|
3
|
+
|
4
|
+
PIN_COLORS = {
|
5
|
+
red: UIColor.redColor,
|
6
|
+
green: UIColor.greenColor,
|
7
|
+
purple: UIColor.purpleColor
|
8
|
+
}
|
9
|
+
|
10
|
+
def screen_setup
|
11
|
+
mapbox_setup
|
12
|
+
self.view = nil
|
13
|
+
self.view = RMMapView.alloc.initWithFrame(self.view.bounds, andTilesource:@tileSource)
|
14
|
+
self.view.delegate = self
|
15
|
+
|
16
|
+
check_annotation_data
|
17
|
+
@promotion_annotation_data = []
|
18
|
+
set_up_tap_to_add
|
19
|
+
end
|
20
|
+
|
21
|
+
def mapbox_setup
|
22
|
+
if self.class.respond_to?(:get_mapbox_setup) && self.class.get_mapbox_setup
|
23
|
+
setup_params = self.class.get_mapbox_setup_params
|
24
|
+
else
|
25
|
+
PM.logger.error "Missing Mapbox setup data."
|
26
|
+
end
|
27
|
+
RMConfiguration.sharedInstance.setAccessToken(setup_params[:access_token])
|
28
|
+
@tileSource = RMMapboxSource.alloc.initWithMapID(setup_params[:tile_source])
|
29
|
+
end
|
30
|
+
|
31
|
+
def view_will_appear(animated)
|
32
|
+
super
|
33
|
+
update_annotation_data
|
34
|
+
end
|
35
|
+
|
36
|
+
def view_did_appear(animated)
|
37
|
+
super
|
38
|
+
set_up_start_position
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_annotation_data
|
42
|
+
PM.logger.error "Missing #annotation_data method in MapScreen #{self.class.to_s}." unless self.respond_to?(:annotation_data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_annotation_data
|
46
|
+
clear_annotations
|
47
|
+
add_annotations annotation_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def map
|
51
|
+
self.view
|
52
|
+
end
|
53
|
+
alias_method :mapview, :map
|
54
|
+
|
55
|
+
def center
|
56
|
+
self.view.centerCoordinate
|
57
|
+
end
|
58
|
+
|
59
|
+
def center=(params={})
|
60
|
+
PM.logger.error "Missing #:latitude property in call to #center=." unless params[:latitude]
|
61
|
+
PM.logger.error "Missing #:longitude property in call to #center=." unless params[:longitude]
|
62
|
+
params[:animated] ||= true
|
63
|
+
|
64
|
+
# Set the new region
|
65
|
+
self.view.setCenterCoordinate(
|
66
|
+
CLLocationCoordinate2D.new(params[:latitude], params[:longitude]),
|
67
|
+
animated:params[:animated]
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def show_user_location
|
72
|
+
if location_manager.respondsToSelector('requestWhenInUseAuthorization')
|
73
|
+
location_manager.requestWhenInUseAuthorization
|
74
|
+
end
|
75
|
+
|
76
|
+
set_show_user_location true
|
77
|
+
end
|
78
|
+
|
79
|
+
def hide_user_location
|
80
|
+
set_show_user_location false
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_show_user_location(show)
|
84
|
+
self.view.showsUserLocation = show
|
85
|
+
end
|
86
|
+
|
87
|
+
def showing_user_location?
|
88
|
+
self.view.showsUserLocation
|
89
|
+
end
|
90
|
+
|
91
|
+
def user_location
|
92
|
+
user_annotation.nil? ? nil : user_annotation.coordinate
|
93
|
+
end
|
94
|
+
|
95
|
+
def user_annotation
|
96
|
+
self.view.userLocation.nil? ? nil : self.view.userLocation.location
|
97
|
+
end
|
98
|
+
|
99
|
+
def zoom_to_user(radius = 0.05, animated=true)
|
100
|
+
show_user_location unless showing_user_location?
|
101
|
+
set_region(create_region(user_location,radius), animated)
|
102
|
+
end
|
103
|
+
|
104
|
+
def annotations
|
105
|
+
@promotion_annotation_data
|
106
|
+
end
|
107
|
+
|
108
|
+
def select_annotation(annotation, animated=true)
|
109
|
+
self.view.selectAnnotation(annotation, animated:animated)
|
110
|
+
end
|
111
|
+
|
112
|
+
def select_annotation_at(annotation_index, animated=true)
|
113
|
+
select_annotation(annotations[annotation_index], animated:animated)
|
114
|
+
end
|
115
|
+
|
116
|
+
def selected_annotation
|
117
|
+
self.view.selectedAnnotation
|
118
|
+
end
|
119
|
+
|
120
|
+
def deselect_annotation(animated=false)
|
121
|
+
unless selected_annotation.nil?
|
122
|
+
self.view.deselectAnnotation(selected_annotation, animated:animated)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_annotation(annotation)
|
127
|
+
@promotion_annotation_data << MapScreenAnnotation.new(annotation,self.view)
|
128
|
+
self.view.addAnnotation @promotion_annotation_data.last
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_annotations(annotations)
|
132
|
+
@promotion_annotation_data = Array(annotations).map{|a| MapScreenAnnotation.new(a,self.view)}
|
133
|
+
self.view.addAnnotations @promotion_annotation_data
|
134
|
+
end
|
135
|
+
|
136
|
+
def clear_annotations
|
137
|
+
@promotion_annotation_data.each do |a|
|
138
|
+
self.view.removeAnnotation(a)
|
139
|
+
end
|
140
|
+
@promotion_annotation_data = []
|
141
|
+
end
|
142
|
+
|
143
|
+
def annotation_view(map_view, annotation)
|
144
|
+
return if annotation.is_a? RMUserLocation
|
145
|
+
|
146
|
+
params = annotation.params
|
147
|
+
|
148
|
+
identifier = params[:identifier]
|
149
|
+
# Set the pin properties
|
150
|
+
if params[:image]
|
151
|
+
view = RMMarker.alloc.initWithUIImage(params[:image])
|
152
|
+
else
|
153
|
+
pinColor = (PIN_COLORS[params[:pin_color]] || params[:pin_color])
|
154
|
+
view = RMMarker.alloc.initWithMapboxMarkerImage(params[:maki_icon], tintColor: pinColor)
|
155
|
+
end
|
156
|
+
view.annotation = annotation
|
157
|
+
view.canShowCallout = params[:show_callout] if view.respond_to?("canShowCallout=")
|
158
|
+
|
159
|
+
if params[:left_accessory]
|
160
|
+
view.leftCalloutAccessoryView = params[:left_accessory]
|
161
|
+
end
|
162
|
+
if params[:right_accessory]
|
163
|
+
view.rightCalloutAccessoryView = params[:right_accessory]
|
164
|
+
end
|
165
|
+
|
166
|
+
if params[:action]
|
167
|
+
button_type = params[:action_button_type] || UIButtonTypeDetailDisclosure
|
168
|
+
|
169
|
+
action_button = UIButton.buttonWithType(button_type)
|
170
|
+
action_button.addTarget(self, action: params[:action], forControlEvents:UIControlEventTouchUpInside)
|
171
|
+
|
172
|
+
view.rightCalloutAccessoryView = action_button
|
173
|
+
end
|
174
|
+
view
|
175
|
+
end
|
176
|
+
|
177
|
+
def set_start_position(params={})
|
178
|
+
params = {
|
179
|
+
latitude: 37.331789,
|
180
|
+
longitude: -122.029620,
|
181
|
+
radius: 10
|
182
|
+
}.merge(params)
|
183
|
+
initialLocation = CLLocationCoordinate2D.new(params[:latitude], params[:longitude])
|
184
|
+
region = create_region(initialLocation,params[:radius])
|
185
|
+
set_region(region, animated:false)
|
186
|
+
end
|
187
|
+
|
188
|
+
def set_up_start_position
|
189
|
+
if self.class.respond_to?(:get_start_position) && self.class.get_start_position
|
190
|
+
self.set_start_position self.class.get_start_position_params
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def set_tap_to_add(params={})
|
195
|
+
params = {
|
196
|
+
length: 2.0,
|
197
|
+
target: self,
|
198
|
+
action: "gesture_drop_pin:",
|
199
|
+
annotation: {
|
200
|
+
title: "Dropped Pin",
|
201
|
+
animates_drop: true
|
202
|
+
}
|
203
|
+
}.merge(params)
|
204
|
+
@tap_to_add_annotation_params = params[:annotation]
|
205
|
+
|
206
|
+
lpgr = UILongPressGestureRecognizer.alloc.initWithTarget(params[:target], action:params[:action])
|
207
|
+
lpgr.minimumPressDuration = params[:length]
|
208
|
+
self.view.addGestureRecognizer(lpgr)
|
209
|
+
end
|
210
|
+
|
211
|
+
def gesture_drop_pin(gesture_recognizer)
|
212
|
+
if gesture_recognizer.state == UIGestureRecognizerStateBegan
|
213
|
+
NSNotificationCenter.defaultCenter.postNotificationName("ProMotionMapWillAddPin", object:nil)
|
214
|
+
touch_point = gesture_recognizer.locationInView(self.view)
|
215
|
+
touch_map_coordinate = self.view.convertPoint(touch_point, toCoordinateFromView:self.view)
|
216
|
+
|
217
|
+
add_annotation({
|
218
|
+
coordinate: touch_map_coordinate
|
219
|
+
}.merge(@tap_to_add_annotation_params))
|
220
|
+
NSNotificationCenter.defaultCenter.postNotificationName("ProMotionMapAddedPin", object:@promotion_annotation_data.last)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def set_up_tap_to_add
|
225
|
+
if self.class.respond_to?(:get_tap_to_add) && self.class.get_tap_to_add
|
226
|
+
self.set_tap_to_add self.class.get_tap_to_add_params
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# TODO: Why is this so complex?
|
231
|
+
def zoom_to_fit_annotations(args={})
|
232
|
+
# Preserve backwards compatibility
|
233
|
+
args = {animated: args} if args == true || args == false
|
234
|
+
args = {animated: true, include_user: false}.merge(args)
|
235
|
+
|
236
|
+
ann = args[:include_user] ? (annotations + [user_annotation]).compact : annotations
|
237
|
+
|
238
|
+
#Don't attempt the rezoom of there are no pins
|
239
|
+
return if ann.count == 0
|
240
|
+
|
241
|
+
#Set some crazy boundaries
|
242
|
+
topLeft = CLLocationCoordinate2D.new(-90, 180)
|
243
|
+
bottomRight = CLLocationCoordinate2D.new(90, -180)
|
244
|
+
|
245
|
+
#Find the bounds of the pins
|
246
|
+
ann.each do |a|
|
247
|
+
topLeft.longitude = [topLeft.longitude, a.coordinate.longitude].min
|
248
|
+
topLeft.latitude = [topLeft.latitude, a.coordinate.latitude].max
|
249
|
+
bottomRight.longitude = [bottomRight.longitude, a.coordinate.longitude].max
|
250
|
+
bottomRight.latitude = [bottomRight.latitude, a.coordinate.latitude].min
|
251
|
+
end
|
252
|
+
|
253
|
+
#Find the bounds of all the pins and set the map_view
|
254
|
+
coord = CLLocationCoordinate2D.new(
|
255
|
+
topLeft.latitude - (topLeft.latitude - bottomRight.latitude) * 0.5,
|
256
|
+
topLeft.longitude + (bottomRight.longitude - topLeft.longitude) * 0.5
|
257
|
+
)
|
258
|
+
|
259
|
+
# Add some padding to the edges
|
260
|
+
span = MKCoordinateSpanMake(
|
261
|
+
((topLeft.latitude - bottomRight.latitude) * 1.075).abs,
|
262
|
+
((bottomRight.longitude - topLeft.longitude) * 1.075).abs
|
263
|
+
)
|
264
|
+
|
265
|
+
region = MKCoordinateRegionMake(coord, span)
|
266
|
+
fits = self.view.regionThatFits(region)
|
267
|
+
|
268
|
+
set_region(fits, animated: args[:animated])
|
269
|
+
end
|
270
|
+
|
271
|
+
def set_region(region, animated=true)
|
272
|
+
self.view.zoomWithLatitudeLongitudeBoundsSouthWest(
|
273
|
+
region[:southWest],
|
274
|
+
northEast: region[:northEast],
|
275
|
+
animated: animated
|
276
|
+
)
|
277
|
+
end
|
278
|
+
|
279
|
+
def deg_to_rad(angle)
|
280
|
+
angle*Math::PI/180
|
281
|
+
end
|
282
|
+
|
283
|
+
def rad_to_deg(angle)
|
284
|
+
angle*180/Math::PI
|
285
|
+
end
|
286
|
+
|
287
|
+
# Input coordinates and bearing in decimal degrees, distance in kilometers
|
288
|
+
def point_from_location_bearing_and_distance(initialLocation, bearing, distance)
|
289
|
+
distance = distance / 6371.01 # Convert to angular radians dividing by the Earth radius
|
290
|
+
bearing = deg_to_rad(bearing)
|
291
|
+
input_latitude = deg_to_rad(initialLocation.latitude)
|
292
|
+
input_longitude = deg_to_rad(initialLocation.longitude)
|
293
|
+
|
294
|
+
output_latitude = Math.asin(
|
295
|
+
Math.sin(input_latitude) * Math.cos(distance) +
|
296
|
+
Math.cos(input_latitude) * Math.sin(distance) *
|
297
|
+
Math.cos(bearing)
|
298
|
+
)
|
299
|
+
|
300
|
+
dlon = input_longitude + Math.atan2(
|
301
|
+
Math.sin(bearing) * Math.sin(distance) *
|
302
|
+
Math.cos(input_longitude), Math.cos(distance) -
|
303
|
+
Math.sin(input_longitude) * Math.sin(output_latitude)
|
304
|
+
)
|
305
|
+
|
306
|
+
output_longitude = (dlon + 3*Math::PI) % (2*Math::PI) - Math::PI
|
307
|
+
CLLocationCoordinate2DMake(rad_to_deg(output_latitude), rad_to_deg(output_longitude))
|
308
|
+
end
|
309
|
+
|
310
|
+
def create_region(initialLocation,radius=10)
|
311
|
+
return nil unless initialLocation.is_a? CLLocationCoordinate2D
|
312
|
+
radius = radius * 1.820 # Meters equivalent to 1 Nautical Mile
|
313
|
+
southWest = self.point_from_location_bearing_and_distance(initialLocation,225, radius)
|
314
|
+
northEast = self.point_from_location_bearing_and_distance(initialLocation,45, radius)
|
315
|
+
{:southWest => southWest, :northEast => northEast}
|
316
|
+
end
|
317
|
+
alias_method :region, :create_region
|
318
|
+
|
319
|
+
def look_up_address(args={}, &callback)
|
320
|
+
args[:address] = args if args.is_a? String # Assume if a string is passed that they want an address
|
321
|
+
|
322
|
+
geocoder = CLGeocoder.new
|
323
|
+
return geocoder.geocodeAddressDictionary(args[:address], completionHandler: callback) if args[:address].is_a?(Hash)
|
324
|
+
return geocoder.geocodeAddressString(args[:address].to_s, completionHandler: callback) unless args[:region]
|
325
|
+
return geocoder.geocodeAddressString(args[:address].to_s, inRegion:args[:region].to_s, completionHandler: callback) if args[:region]
|
326
|
+
end
|
327
|
+
|
328
|
+
def look_up_location(location, &callback)
|
329
|
+
location = CLLocation.alloc.initWithLatitude(location.latitude, longitude:location.longitude) if location.is_a?(CLLocationCoordinate2D)
|
330
|
+
|
331
|
+
if location.kind_of?(CLLocation)
|
332
|
+
geocoder = CLGeocoder.new
|
333
|
+
geocoder.reverseGeocodeLocation(location, completionHandler: callback)
|
334
|
+
else
|
335
|
+
PM.logger.info("You're trying to reverse geocode something that isn't a CLLocation")
|
336
|
+
callback.call nil, nil
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
########## Mapbox methods #################
|
341
|
+
def mapView(map_view, layerForAnnotation: annotation)
|
342
|
+
annotation_view(map_view, annotation)
|
343
|
+
end
|
344
|
+
|
345
|
+
########## Cocoa touch methods #################
|
346
|
+
def mapView(map_view, didUpdateUserLocation:userLocation)
|
347
|
+
if self.respond_to?(:on_user_location)
|
348
|
+
on_user_location(userLocation)
|
349
|
+
else
|
350
|
+
PM.logger.info "You're tracking the user's location but have not implemented the #on_user_location(location) method in MapScreen #{self.class.to_s}."
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def mapView(map_view, regionWillChangeAnimated:animated)
|
355
|
+
if self.respond_to?("will_change_region:")
|
356
|
+
will_change_region(animated)
|
357
|
+
elsif self.respond_to?(:will_change_region)
|
358
|
+
will_change_region
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def mapView(map_view, regionDidChangeAnimated:animated)
|
363
|
+
if self.respond_to?("on_change_region:")
|
364
|
+
on_change_region(animated)
|
365
|
+
elsif self.respond_to?(:on_change_region)
|
366
|
+
on_change_region
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
########## Cocoa touch Ruby counterparts #################
|
371
|
+
|
372
|
+
def deceleration_mode
|
373
|
+
map.decelerationMode
|
374
|
+
end
|
375
|
+
|
376
|
+
def deceleration_mode=(mode)
|
377
|
+
map.decelerationMode = mode
|
378
|
+
end
|
379
|
+
|
380
|
+
%w(dragging bouncing clustering).each do |meth|
|
381
|
+
define_method("#{meth}_enabled?") do
|
382
|
+
map.send("#{meth}Enabled")
|
383
|
+
end
|
384
|
+
|
385
|
+
define_method("#{meth}_enabled=") do |argument|
|
386
|
+
map.send("#{meth}Enabled=", argument)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
module MapClassMethods
|
391
|
+
# Start Position
|
392
|
+
def start_position(params={})
|
393
|
+
@start_position_params = params
|
394
|
+
@start_position = true
|
395
|
+
end
|
396
|
+
|
397
|
+
def get_start_position_params
|
398
|
+
@start_position_params ||= nil
|
399
|
+
end
|
400
|
+
|
401
|
+
def get_start_position
|
402
|
+
@start_position ||= false
|
403
|
+
end
|
404
|
+
|
405
|
+
# Tap to drop pin
|
406
|
+
def tap_to_add(params={})
|
407
|
+
@tap_to_add_params = params
|
408
|
+
@tap_to_add = true
|
409
|
+
end
|
410
|
+
|
411
|
+
def get_tap_to_add_params
|
412
|
+
@tap_to_add_params ||= nil
|
413
|
+
end
|
414
|
+
|
415
|
+
def get_tap_to_add
|
416
|
+
@tap_to_add ||= false
|
417
|
+
end
|
418
|
+
|
419
|
+
# Mapbox setup
|
420
|
+
def mapbox_setup(params={})
|
421
|
+
@mapbox_setup_params = params
|
422
|
+
@mapbox_setup = true
|
423
|
+
end
|
424
|
+
|
425
|
+
def get_mapbox_setup_params
|
426
|
+
@mapbox_setup_params ||= nil
|
427
|
+
end
|
428
|
+
|
429
|
+
def get_mapbox_setup
|
430
|
+
@mapbox_setup ||= false
|
431
|
+
end
|
432
|
+
|
433
|
+
|
434
|
+
end
|
435
|
+
def self.included(base)
|
436
|
+
base.extend(MapClassMethods)
|
437
|
+
end
|
438
|
+
|
439
|
+
private
|
440
|
+
|
441
|
+
def location_manager
|
442
|
+
@location_manager ||= CLLocationManager.alloc.init
|
443
|
+
@location_manager.delegate ||= self
|
444
|
+
@location_manager
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ProMotion-mapbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Diogo Andre
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ProMotion
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: motion-cocoapods
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: motion-stump
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: motion-redgreen
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Adds PM::MapScreen support to ProMotion, using Mapbox as map provider.
|
84
|
+
email:
|
85
|
+
- diogo@regattapix.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- README.md
|
91
|
+
- lib/ProMotion/map/map_screen.rb
|
92
|
+
- lib/ProMotion/map/map_screen_annotation.rb
|
93
|
+
- lib/ProMotion/map/map_screen_module.rb
|
94
|
+
- lib/ProMotion-mapbox.rb
|
95
|
+
homepage: https://github.com/diogoandre/ProMotion-mapbox
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.0.6
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Adds PM::MapScreen support to ProMotion, using Mapbox as map provider. Forked
|
119
|
+
from Promotion-map
|
120
|
+
test_files: []
|