Showing Multiple Approximate Locations on a Map

Continuing the discussion from Showing approximate location on a map:

Using this post as a template, I would like to now populate the map with multiple items from a repeating panel. I have a Data Table “Locations” with 2 columns “House” and “Address”, that populates a repeating panel beside the map.

I made a simple server function that just returns all the rows from the Data Table, and I thought maybe I could just call that function and pull out the column “Address”, which I tried like so:

  def multiple_fuzzy_location(self):
    lats_lngs = anvil.server.call('show_houses')['address']
    print lats_lngs

But ended up with this error:

Exception: Indexing with [] is not supported on this anvil.tables.SearchIterator 

Is this the right way to try to extract a single column?

My plan is to then run each address through the Geocode server function, and populate the map with a new marker in each iteration.

https://anvil.works/build#clone:5DMVI4MOT7QAGQQJ=DIB66TMULQXNBXKZVXT43RKO

Moving to Q&A

Woops sorry about that thank you!

1 Like

For a single row, if you want to return a single column you have to use my_table.get()['my_column]. If you use my_table.search(), you will get a search iterator which would contain potentially multiple rows.

I think of the search iterator as a list of dictionaries where each item in the list is a row from your data table. Therefore, in that case, if you want to pull out a single column, you would have to numerically index the list first (as that is the row), and then you can extract the column (or in dictionary terms, the value). Like so: my_table.search()[0]['my_column]

To get all addresses in one list, you can use:

addresses = [r['address'] for r in app_tables.locations.search()]

I hope this helps.

1 Like

Okay, progress! Thank you for your response @alcampopiano, it sent me in the right direction.

I modified my server function (using your tip above) to be this:

@anvil.server.callable
def get_adds(houses):
  geolocator = ArcGIS()
  adds = [geolocator.geocode(r['address']) for r in app_tables.locations.search()]
  print(adds)
  return adds

Which seems to work well, and it returns this:

[Location(28 South Dr, Toronto, Ontario, M4W 1R1, (43.67726603074731, -79.3821840548228, 0.0)), Location(53 Northbrook Rd, East York, Toronto, Ontario, M4J 4G2, (43.69471073189449, -79.33772748915129, 0.0)), Location(5795 Yonge St, Unit 804, North York, Toronto, Ontario, M2M 3T9, (43.785306917768445, -79.41487594549575, 0.0))]

But back on my main form, I get this error:

anvil.server.SerializationError: Cannot serialize return value from function. Cannot serialize <class 'geopy.location.Location'> object at msg['response'][0]
at Form1, line 46

Called from this part of the form:

def multiple_fuzzy_location(self):
    houses = anvil.server.call('show_houses')
    lats, lngs = anvil.server.call('get_adds', houses)
    print(lats,lngs)

Somehow I just need to extract the lat and lon coordinates from the return of the server function and then plot that on the map. How should I modify this specific line which is giving me the error?

lats, lngs = anvil.server.call('get_adds', houses)

https://anvil.works/build#clone:5DMVI4MOT7QAGQQJ=DIB66TMULQXNBXKZVXT43RKO

My guess is that you will have to cast your location objects to a type that Anvil can pass to the client.

I have not tried the code since im on my cell. Can you cast your lat/longs and/or location objects to numbers and/or strings?

Just trying to figure out how to do that now… okay so I have my location object “adds” that is returned from the server function.

To cast the lat/longs from “adds” to numbers would the syntax be something like this?

lats_lngs = adds['address']

But when I run that I get an error saying

TypeError: list indices must be integers or slices, not str

I don’t know much about those location objects, but typically you cast things like this:

Cast something to a string:
str(my_object)

Cast to a float:
float(my_object)

Cast to an integer:
int(my_object)

“my_object” would be whatever you want to cast to a new type

Or the location objects may have methods that will allow you to get the lat/lons, in which case you wouldn’t need to cast

1 Like

In your previous code example:

@anvil.server.callable
def get_adds(houses):
  geolocator = ArcGIS()
  adds = [geolocator.geocode(r['address']) for r in app_tables.locations.search()]
  print(adds)
  return adds

adds is a list. So you can access its elements using an integer, but not a string.

If you want to get the latitude and longitude of each element, you can do this:

for location in adds:
    lat, lng = location.latitude, location.longitude

To answer your main question: there’s a limited set of things you can return from a server function. This section of the ref docs tells you what you can return. So as @alcampopiano says, you need to convert your data to a format that can be returned: a list of tuples should do it. Try replacing your list comprehension with this:

  adds = []
  for r in app_tables.locations.search():
    location = geolocator.geocode(r['address'])
    adds.append((location.latitude, location.longitude))
1 Like

Thank you very much @shaun and @alcampopiano! Got it working now, awesome.

Ended up using this for my server function:

@anvil.server.callable
def get_adds():
  geolocator = ArcGIS()
  adds = []
  
  for r in app_tables.locations.search():
    location = geolocator.geocode(r['address'])
    adds.append((location.latitude, location.longitude))
  return adds

And this to call it from the main form:

def multiple_fuzzy_location(self):
    adds = anvil.server.call('get_adds')
    print(adds)
    for lat, lng in adds:
      
      offset_lat = 200./111111. * random.uniform(-0.5, 0.5)   # Random offset of approx 200 meters
      offset_lng = 200./(111111.*cos(lat)) * random.uniform(-0.5, 0.5)   # Random offset of approx 200 meters
      
      self.marker = GoogleMap.Circle(
        center=GoogleMap.LatLng(lat+offset_lat, lng+offset_lng),
        radius=100)
      
      self.map_1.add_component(self.marker)

It’s working!! Still need to play around with how to centre the map to make sure all circles can be seen but overall it is working. Thanks again!

Here is a copy in case anybody in the future who sees this wants to take a look.

https://anvil.works/build#clone:5DMVI4MOT7QAGQQJ=DIB66TMULQXNBXKZVXT43RKO

2 Likes