Forum Archive

Interactive visualization

RichardSmith

Hello everyone, I am working with python quite a while now and am now faced with the task to program an interactive map visualization over time. I am working at an NGO which supports children in Uganda and we want to visualize all supported children and projects over time on a map of the area (animated). Furthermore some information about the projects (since when, how it helped) and children (age, supported since, etc.) should be shown when hovering or kicking the according dot on ths map. Which libraries and other things do you recommend for this project? Data is stored and edited as a CSV file including coordinates and other important information. Thank you for your help!

cvp

@RichardSmith see an example here

cvp

Easier way to begin

mikael

@RichardSmith, I know this might sound odd in our forum, but if this is something you need to work on PCs and maybe send to others etc., I would suggest the map features of Excel.

cvp

@mikael said:

if this is something you need to work on PCs

In this case, I agree but if he already knows Python and asks it for Pythonista...

cvp

@RichardSmith Shortest script


import console
from objc_util import *
import ctypes
import ui

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)]

MKPointAnnotation = ObjCClass('MKPointAnnotation')
MKPinAnnotationView = ObjCClass('MKPinAnnotationView')
MKAnnotationView = ObjCClass('MKAnnotationView')
UIColor = ObjCClass('UIColor') # used to set pin color

def mapView_viewForAnnotation_(self,cmd,mk_mapview,mk_annotation):
    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())
            pin_color = annotation.pin_color
            id = tit
            pinView = mapView.dequeueReusableAnnotationViewWithIdentifier_(id)

            if not pinView:
                pinView = MKPinAnnotationView.alloc().initWithAnnotation_reuseIdentifier_(annotation,id)
                pinView.canShowCallout = True   # tap-> show title
                pinView.animatesDrop   = False   # not Animated pin falls like a drop
                pinView.pinColor = pin_color # 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)    
    #MyMapViewDelegate = create_objc_class('MyMapViewDelegate', NSObject, methods=methods, protocols=protocols) 
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)
        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()

        # Set Delegate of mk_map_view
        map_delegate = MyMapViewDelegate.alloc().init()#.autorelease()
        self.mk_map_view.setDelegate_(map_delegate)


    @on_main_thread
    def add_pin(self, lat, lon, color, title, subtitle):
        global all_points
        '''Add a pin annotation to the map'''
        MKPointAnnotation = ObjCClass('MKPointAnnotation')
        coord = CLLocationCoordinate2D(lat, lon)
        all_points.append(coord)            # store all pin's for MKPolyline
        annotation = MKPointAnnotation.alloc().init().autorelease()
        annotation.setTitle_(title)
        annotation.setSubtitle_(str(subtitle))
        annotation.setCoordinate_(coord, restype=None, argtypes=[CLLocationCoordinate2D])
        annotation.pin_color = color
        self.mk_map_view.addAnnotation_(annotation)


    @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])

def main():
    global all_points

    #----- Main process -----
    console.clear()

    # Hide script
    back = MapView()
    back.background_color='white'
    back.name = 'Test for @RichardSmith'
    back.present('fullscreen', hide_title_bar=False)

    map_points = []
    # assume infos come from a CSV file
    map_points.append((0.5830414, 32.5316170, 0, 'Yoweri Museveni', '12 years, supported since 01/02/2019'))
    map_points.append((0.2986261, 32.6033583, 1, "The Uganda National N.G.O Forum", 'since 1997, Providing a Sharing and Reflection Platform for NGOs in Uganda'))

    # min and max of latitude and longitude
    min_lat = min(map_points,key = lambda x:x[0])[0]
    max_lat = max(map_points,key = lambda x:x[0])[0]
    min_lon = min(map_points,key = lambda x:x[1])[1]
    max_lon = max(map_points,key = lambda x:x[1])[1]
    # Display map, center and zoom so all points are visible
    back.set_region((min_lat+max_lat)/2,(min_lon+max_lon)/2, 4*(max_lat-min_lat), 4*(max_lon-min_lon), animated=True)
    # Display pin's
    all_points = []
    for point in map_points:
        back.add_pin(point[0],point[1],point[2],point[3],point[4])

# Protect against import    
if __name__ == '__main__':
    main()
cvp

Add these lines if you want multiple lines in the subtitle

.
.
.

                pinView.pinColor = pin_color # 0=red 1=green 2=purple
                # add these lines if you want several lines in subtitle: begin  
                l_title = ui.Label()
                l_title.font = ('Menlo',12) # police echappement fixe
                lo = ObjCInstance(l_title)
                l_title.text = subtit.replace(',','\n')
                lo.setNumberOfLines_(0)
                pinView.setDetailCalloutAccessoryView_(lo)      
                # add these lines if you want several lines in subtitle: end

jasper

you are right but I also have the solution. But it's different. chatiw bazoocam camzap