omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Map API, need suggestions for how to execute ideas

    Pythonista
    4
    196
    69668
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • cvp
      cvp @RocketBlaster05 last edited by cvp

      @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()
      
      RocketBlaster05 1 Reply Last reply Reply Quote 0
      • RocketBlaster05
        RocketBlaster05 @cvp last edited by

        @cvp Yes this is very helpful. I will need to adjust the button function but that is really easy, and I'll change the presentation to "sheet". I will need to modify the code so that it automatically gets centered on the user's location like omz's second version of the map. If you could guide me towards how to do that, that would be amazing.

        cvp 3 Replies Last reply Reply Quote 0
        • cvp
          cvp @RocketBlaster05 last edited by

          @RocketBlaster05 That will not be for today...sorry
          Don't you want to center the map on the center of all previous pin's?

          1 Reply Last reply Reply Quote 0
          • cvp
            cvp @RocketBlaster05 last edited by

            @RocketBlaster05 did you try several runs with multiple pin's, like

            1 Reply Last reply Reply Quote 0
            • ccc
              ccc last edited by

              #!python2 ??

              cvp 3 Replies Last reply Reply Quote 0
              • cvp
                cvp @ccc last edited by

                @ccc old line of omz

                1 Reply Last reply Reply Quote 0
                • cvp
                  cvp @ccc last edited by

                  @ccc works without the line

                  1 Reply Last reply Reply Quote 1
                  • cvp
                    cvp @ccc last edited by cvp

                    @ccc you know my standard response to your advices: you're always right 😇

                    1 Reply Last reply Reply Quote 0
                    • cvp
                      cvp @RocketBlaster05 last edited by cvp

                      @RocketBlaster05 said:

                      automatically gets centered on the user's location

                      def main():
                      	.
                      	.
                      	.
                      	# center on user location				
                      	import location
                      	location.start_updates()
                      	time.sleep(1)
                      	loc = location.get_location()
                      	location.stop_updates()
                      	if loc:
                      		lat, lon = loc['latitude'], loc['longitude']
                      		v.set_region(lat, lon, 0.05, 0.05, animated=True)
                      
                      if __name__ == '__main__':
                      	main()
                      

                      Next lesson : zoom so all previous pin's are visible, interested?

                      RocketBlaster05 1 Reply Last reply Reply Quote 0
                      • RocketBlaster05
                        RocketBlaster05 @cvp last edited by

                        @cvp yes! that would be very helpful. I am very thankful you are willing to help out!

                        cvp 1 Reply Last reply Reply Quote 0
                        • cvp
                          cvp @RocketBlaster05 last edited by

                          @RocketBlaster05

                          def compute_region_param(l):
                          	# Compute min and max of latitude and longitude
                          	min_lat = min(l,key = lambda x:x[0])[0]
                          	max_lat = max(l,key = lambda x:x[0])[0]
                          	min_lon = min(l,key = lambda x:x[1])[1]
                          	max_lon = max(l,key = lambda x:x[1])[1]
                          	d_lat = 1.2*(max_lat-min_lat)
                          	d_lon = 1.2*(max_lon-min_lon)
                          	return min_lat,min_lon,max_lat,max_lon,d_lat,d_lon
                          
                          def main():
                          	.
                          	.
                          	.
                          	# center on user location				
                          	import location
                          	location.start_updates()
                          	time.sleep(1)
                          	loc = location.get_location()
                          	location.stop_updates()
                          	if loc:
                          		lat, lon = loc['latitude'], loc['longitude']
                          		if locs:
                          			min_lat,min_lon,max_lat,max_lon,d_lat,d_lon = compute_region_param(locs)
                          			v.set_region((min_lat+max_lat)/2, (min_lon+max_lon)/2,1.2*(max_lat-min_lat), 1.2*(max_lon-min_lon), animated=True)	
                          		else:
                          			v.set_region(lat, lon, 0.05, 0.05, animated=True)
                          
                          
                          RocketBlaster05 1 Reply Last reply Reply Quote 0
                          • RocketBlaster05
                            RocketBlaster05 @cvp last edited by

                            @cvp this is very good. One more thing for now: I want to make the user's location the purple pin instead of the green/red so that it stands out. I have the location as 'Current Location' for the pin. Any ideas? Thanks

                            cvp 4 Replies Last reply Reply Quote 0
                            • cvp
                              cvp @RocketBlaster05 last edited by cvp

                              @RocketBlaster05 said:

                              user's location the purple pin

                              Be careful: if you change something in the mapView_viewForAnnotation_ def, you need to restart Pythonista because the class is already built and kept in memory (restart means remove it from the tasks list and relaunch it)

                              Did you remark that if you tap on a pin, you get its title and location.

                              Édit: try to avoid to have in the .loc file a point with the same coordinates as the user location,
                              because you could have a purple and a green pin at exactly same position and only see one, even with a big zoom.

                              def mapView_viewForAnnotation_(self,cmd,mk_mapview,mk_annotation):
                              	.
                              	.
                              	.
                              				if tit == 'Dropped Pin':
                              					pinView.pinColor = 0 # 0=red 1=green 2=purple
                              				elif tit == 'Current Location':
                              					pinView.pinColor = 2 # 0=red 1=green 2=purple
                              				else:
                              					pinView.pinColor = 1 # 0=red 1=green 2=purple
                              .
                              .
                              .
                              	loc = location.get_location()
                              	location.stop_updates()
                              	if loc:
                              		lat, lon = loc['latitude'], loc['longitude']
                              		# add a purple pin for user's location
                              		v.add_pin(lat, lon, 'Current Location', str((lat, lon)))
                              		# don't save it in file but add it in locs for centering zoom
                              		locs.append((lat,lon))
                              		if locs:
                              			min_lat,min_lon,max_lat,max_lon,d_lat,d_lon = compute_region_param(locs)
                              			v.set_region((min_lat+max_lat)/2, (min_lon+max_lon)/2,1.2*(max_lat-min_lat), 1.2*(max_lon-min_lon), animated=True)	
                              		else:
                              			v.set_region(lat, lon, 0.05, 0.05, animated=True)
                              
                              
                              1 Reply Last reply Reply Quote 0
                              • ccc
                                ccc last edited by

                                pin_colors = {
                                    "Dropped Pin": 0,  # red
                                    "Current Location": 2,  # purple
                                }
                                …
                                pin_color = pin_colors.get(title, 1)  # default to green
                                
                                cvp 1 Reply Last reply Reply Quote 0
                                • cvp
                                  cvp @ccc last edited by cvp

                                  @ccc said:

                                  pin_color = pin_colors.get(title, 1) # default to green

                                  pinView.pin_color = pin_colors.get(tit, 1)  # default to green
                                  
                                  1 Reply Last reply Reply Quote 0
                                  • cvp
                                    cvp @RocketBlaster05 last edited by

                                    @RocketBlaster05 and tell me if you want to see your own photo instead of a purple pin

                                    RocketBlaster05 1 Reply Last reply Reply Quote 0
                                    • cvp
                                      cvp @RocketBlaster05 last edited by cvp

                                      @RocketBlaster05 or, if you prefer,

                                      Or, with with some drops of blood 😂

                                      1 Reply Last reply Reply Quote 0
                                      • cvp
                                        cvp @RocketBlaster05 last edited by cvp

                                        @RocketBlaster05 and if you want to be able to set view type (satellite, flyover...), you can find this here

                                        See two buttons above right of the map

                                        1 Reply Last reply Reply Quote 1
                                        • RocketBlaster05
                                          RocketBlaster05 @cvp last edited by RocketBlaster05

                                          @cvp said:

                                          @RocketBlaster05 and tell me if you want to see your own photo instead of a purple pin

                                          This is a great idea. I would like to incorporate this to make the map feel more personal. This may be a big request but can you also show me how to go about making the map start automatically instead of having to press the button? I would like to make it so the button will mark somebody’s location with an image of like a trash bag. Also, is there a simple command I can use to make the map update somebody’s position? I haven’t tried moving around with the map open so if it already moves automatically then that’s my bad for not checking. Thanks in advance.

                                          I would like for it to show the face on a little pin, like you showed with the blood. I would like for the memoji to be able to have the trash bag I mentioned before just below it like the blood drop. If that is possible, that would be exactly what I need for my pins.

                                          cvp 1 Reply Last reply Reply Quote 0
                                          • ccc
                                            ccc last edited by ccc

                                            An image of like a trash bag… 🚮🗑♻️☠️🤢💰💩🤮

                                            cvp 1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors