@RocketBlaster05 try this quick and dirty example.
Press the map button to show or hide the map
Long press add a new location, temporary as "dropped" then as "user point" at next long press.
Locations are saved in a file (here a.loc) and when you restart the script, previous points are shown on the map as green pin's, dropped is red.
Hoping this helps (only if I have correctly understood your request, maybe I could be wrong as usual š¤)
Edit: source has just been modified for pin's color
#!python2
'''
NOTE: This requires the latest beta of Pythonista 1.6 (build 160022)
Demo of a custom ui.View subclass that embeds a native map view using MapKit (via objc_util). Tap and hold the map to drop a pin.
The MapView class is designed to be reusable, but it doesn't implement *everything* you might need. I hope that the existing methods give you a basic idea of how to add new capabilities though. For reference, here's Apple's documentation about the underlying MKMapView class: http://developer.apple.com/library/ios/documentation/MapKit/reference/MKMapView_Class/index.html
If you make any changes to the OMMapViewDelegate class, you need to restart the app. Because this requires creating a new Objective-C class, the code can basically only run once per session (it's not safe to delete an Objective-C class at runtime as long as instances of the class potentially exist).
'''
from objc_util import *
import ast
import ctypes
import ui
import location
import os
import time
import weakref
MKPointAnnotation = ObjCClass('MKPointAnnotation')
MKPinAnnotationView = ObjCClass('MKPinAnnotationView')
UIColor = ObjCClass('UIColor') # used to set pin color
def mapView_viewForAnnotation_(self,cmd,mk_mapview,mk_annotation):
global map_pin_type, map_pin_color, map_addr_color, map_pin_size, map_pin_radius, map_pin_borderwidth, map_pin_bordercolor, contacts_photos
try:
# not specially called in the same sequence as pins created
# should have one MK(Pin)AnnotationView by type (ex: pin color)
annotation = ObjCInstance(mk_annotation)
mapView = ObjCInstance(mk_mapview)
if annotation.isKindOfClass_(MKPointAnnotation):
tit = str(annotation.title())
subtit = str(annotation.subtitle())
id = tit
pinView = mapView.dequeueReusableAnnotationViewWithIdentifier_(id)
if not pinView:
# Modify pin color: use MKPinAnnotationView
pinView = MKPinAnnotationView.alloc().initWithAnnotation_reuseIdentifier_(annotation,id)
pinView.canShowCallout = True # tap-> show title
pinView.animatesDrop = True # Animated pin falls like a drop
if tit == 'Dropped Pin':
pinView.pinColor = 0 # 0=red 1=green 2=purple
else:
pinView.pinColor = 1 # 0=red 1=green 2=purple
else:
pinView.annotation = annotation
return pinView.ptr
return None
except Exception as e:
print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e)
# Build method of MKMapView Delegate
methods = [mapView_viewForAnnotation_]
protocols = ['MKMapViewDelegate']
try:
MyMapViewDelegate = ObjCClass('MyMapViewDelegate')
except Exception as e:
MyMapViewDelegate = create_objc_class('MyMapViewDelegate', methods=methods, protocols=protocols)
# _map_delegate_cache is used to get a reference to the MapView from the (Objective-C) delegate callback. The keys are memory addresses of `OMMapViewDelegate` (Obj-C) objects, the values are `ObjCInstance` (Python) objects. This mapping is necessary because `ObjCInstance` doesn't guarantee that you get the same object every time when you instantiate it with a pointer (this may change in future betas). MapView stores a weak reference to itself in the specific `ObjCInstance` that it creates for its delegate.
_map_delegate_cache = weakref.WeakValueDictionary()
class CLLocationCoordinate2D (Structure):
_fields_ = [('latitude', c_double), ('longitude', c_double)]
class MKCoordinateSpan (Structure):
_fields_ = [('d_lat', c_double), ('d_lon', c_double)]
class MKCoordinateRegion (Structure):
_fields_ = [('center', CLLocationCoordinate2D), ('span', MKCoordinateSpan)]
class MapView (ui.View):
@on_main_thread
def __init__(self, *args, **kwargs):
ui.View.__init__(self, *args, **kwargs)
MKMapView = ObjCClass('MKMapView')
frame = CGRect(CGPoint(0, 0), CGSize(self.width, self.height))
self.mk_map_view = MKMapView.alloc().initWithFrame_(frame)
#print(dir(self.mk_map_view.region()))
flex_width, flex_height = (1<<1), (1<<4)
self.mk_map_view.setAutoresizingMask_(flex_width|flex_height)
self_objc = ObjCInstance(self)
self_objc.addSubview_(self.mk_map_view)
self.mk_map_view.release()
self.long_press_action = None
self.scroll_action = None
#NOTE: The button is only used as a convenient action target for the gesture recognizer. While this isn't documented, the underlying UIButton object has an `-invokeAction:` method that takes care of calling the associated Python action.
self.gesture_recognizer_target = ui.Button()
self.gesture_recognizer_target.action = self.long_press
UILongPressGestureRecognizer = ObjCClass('UILongPressGestureRecognizer')
self.recognizer = UILongPressGestureRecognizer.alloc().initWithTarget_action_(self.gesture_recognizer_target, sel('invokeAction:')).autorelease()
self.mk_map_view.addGestureRecognizer_(self.recognizer)
self.long_press_location = ui.Point(0, 0)
self.map_delegate = MyMapViewDelegate.alloc().init()#.autorelease()
self.mk_map_view.setDelegate_(self.map_delegate)
self.map_delegate.map_view_ref = weakref.ref(self)
_map_delegate_cache[self.map_delegate.ptr] = self.map_delegate
def long_press(self, sender):
#NOTE: The `sender` argument will always be the dummy ui.Button that's used as the gesture recognizer's target, just ignore it...
gesture_state = self.recognizer.state()
if gesture_state == 1 and callable(self.long_press_action):
loc = self.recognizer.locationInView_(self.mk_map_view)
self.long_press_location = ui.Point(loc.x, loc.y)
self.long_press_action(self)
@on_main_thread
def add_pin(self, lat, lon, title, subtitle=None, select=False):
'''Add a pin annotation to the map'''
MKPointAnnotation = ObjCClass('MKPointAnnotation')
coord = CLLocationCoordinate2D(lat, lon)
annotation = MKPointAnnotation.alloc().init().autorelease()
annotation.setTitle_(title)
if subtitle:
annotation.setSubtitle_(subtitle)
annotation.setCoordinate_(coord, restype=None, argtypes=[CLLocationCoordinate2D])
self.mk_map_view.addAnnotation_(annotation)
if select:
self.mk_map_view.selectAnnotation_animated_(annotation, True)
@on_main_thread
def remove_all_pins(self):
'''Remove all annotations (pins) from the map'''
self.mk_map_view.removeAnnotations_(self.mk_map_view.annotations())
@on_main_thread
def set_region(self, lat, lon, d_lat, d_lon, animated=False):
'''Set latitude/longitude of the view's center and the zoom level (specified implicitly as a latitude/longitude delta)'''
region = MKCoordinateRegion(CLLocationCoordinate2D(lat, lon), MKCoordinateSpan(d_lat, d_lon))
self.mk_map_view.setRegion_animated_(region, animated, restype=None, argtypes=[MKCoordinateRegion, c_bool])
@on_main_thread
def set_center_coordinate(self, lat, lon, animated=False):
'''Set latitude/longitude without changing the zoom level'''
coordinate = CLLocationCoordinate2D(lat, lon)
self.mk_map_view.setCenterCoordinate_animated_(coordinate, animated, restype=None, argtypes=[CLLocationCoordinate2D, c_bool])
@on_main_thread
def get_center_coordinate(self):
'''Return the current center coordinate as a (latitude, longitude) tuple'''
coordinate = self.mk_map_view.centerCoordinate(restype=CLLocationCoordinate2D, argtypes=[])
return coordinate.latitude, coordinate.longitude
@on_main_thread
def point_to_coordinate(self, point):
'''Convert from a point in the view (e.g. touch location) to a latitude/longitude'''
coordinate = self.mk_map_view.convertPoint_toCoordinateFromView_(CGPoint(*point), self._objc_ptr, restype=CLLocationCoordinate2D, argtypes=[CGPoint, c_void_p])
return coordinate.latitude, coordinate.longitude
def _notify_region_changed(self):
if callable(self.scroll_action):
self.scroll_action(self)
# --------------------------------------
# DEMO:
def long_press_action(sender):
global locs,path
# Add a pin when the MapView recognizes a long-press
c = sender.point_to_coordinate(sender.long_press_location)
# this of only to special process asked in forum
# https://forum.omz-software.com/topic/7077/removing-custom-pins-with-map-api
for annotation in sender.mk_map_view.annotations():
if str(annotation.title()) == 'Dropped Pin':
sender.mk_map_view.removeAnnotation_(annotation)
prev_lat,prev_lon = locs[-1]
sender.add_pin(prev_lat, prev_lon, 'user point', str((prev_lat, prev_lon)))
else:
sender.add_pin(c[0], c[1], 'Dropped Pin', str(c), select=True)
sender.set_center_coordinate(c[0], c[1], animated=True)
locs.append(c)
with open(path,mode='wt') as f:
content = str(locs)
f.write(content)
def scroll_action(sender):
# Show the current center coordinate in the title bar after the map is scrolled/zoomed:
sender.name = 'lat/long: %.2f, %.2f' % sender.get_center_coordinate()
def main():
global locs,path
# create main view
mv = ui.View()
mv.name = 'Map for RocketBlaster05'
mv.background_color = 'white'
mv.present('fullscreen')
w,h = ui.get_screen_size()
# Create and present a MapView:
v = MapView(frame=(0,0,w,h-76))
v.hidden = True
mv.add_subview(v)
bmap = ui.ButtonItem()
bmap.image = ui.Image.named('iob:map_32')
mv.right_button_items = (bmap,)
def bmap_action(sender):
v.hidden = not v.hidden
bmap.action = bmap_action
v.long_press_action = long_press_action
v.scroll_action = scroll_action
path = 'a.loc'
if not os.path.exists(path):
locs = []
else:
with open(path,mode='rt') as f:
content = f.read()
locs = ast.literal_eval(content)
for lat,lon in locs:
v.add_pin(lat, lon, 'user point', str((lat, lon)))
if __name__ == '__main__':
main()