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
    69950
    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.
    • RocketBlaster05
      RocketBlaster05 last edited by RocketBlaster05

      Hello! I figured I should make this a new post so more eyes can see it. I am attempting to modify the code of the default python 3 map API example from omz so that I can do the following:

      I have a simple ui.load_view() I plan to make later which will ask the user if they want to mark their current location on the map. However I would need a button to be pressed for the user to get the view to work. Also, the user would need to be able to do so as many times as they like, so that multiple marks can appear on the map. The positions of the markers would need to be saved so that the next time the map is activated, the markers will reappear.

      I understand this is likely a fair amount of code, but any help would be greatly appreciated. If you could suggest any ideas for me to test later I would be very thankful.

      cvp 1 Reply Last reply Reply Quote 0
      • 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
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors