Forum Archive

Unable to use location.reverse_geocode inside a scene/layer

lenoirmind

Hello all,

First of: what an inspiring app Pythonista is! I am a developer, though Python is pretty new to me. But I am a quick learner and start to really enjoy Python.

I run into a strange problem and I do not really understand what I am doing wrong or what might go wrong.

I've succesfully made simple sketching application based on the scene module elaborating on the 'example with layers'

I've also succeeded in using the location module and retrieve my current location and made pythonista show me the address of my current location in the console.

But when I try to use the same location code to retrieve and display my current addres in the scene mode, in either the console (displayed after closing the scene) or using the text() command in the scene, I only get 'none' as a result. Retrieving the gps coordinated does work from within a scene. But location.reverse_gecode does not.

Did anyone run into this as well?

Thanks in advance for the time and effort helping me out here...

ccc

I saw the same behavior as you. In a scene, loc = location.get_location() works as expected but location.reverse_geocode(loc) always returns None.

lenoirmind

that, in a way is good news.
I would be glad to help out here, but my knowledge about Python is not sufficient yet.
Something with assignment and retrieval of global variables and class and/or function scopes within the scene module?
I've had similar problems in php/java based systems when juggling with packages and static (global) variables and static class variables.

dgelessus

I can confirm this as well. In a simple Python script all location functionality works as expected, but when called from inside a Scene neither geocode() nor reverse_geocode() work and return None. Wrapping the geocode call in a global function or separate module also doesn't appear to help.

ccc

Of course the work around is to gather the data before calling Scene.run()

lenoirmind

Thanks for your time, both dgelessus snd ccc

Agree.

Location.geocode seems to work perfectly within a scene running. at least in my app. Only problem might be if you'd want to display the changing address in the scene while walking around using location.reverse_geocode.

but there may be workarounds for that as well - different rev_gecode services?

anyway... in my view this is exactly why i really like pythonista!

dgelessus

Interesting, for me location.geocode() also doesn't work in a scene. I'm using the example address dict from the location module docs, which geocodes fine in a regular script.

Regarding a workaround, is there a way to softly stop scene running/rendering and execute code outside the scene class again? So far I couldn't find any way to do that without terminating the entire program. If this is possible, the scene could simply be stopped and the location reverse-geocoded and stored in a global variable, which could then be read inside the scene again. Of course this wouldn't be very clean and would probably also reset parts of the scene, but it might work.

For comparison, here's the test program that I've been using: http://pastebin.com/ZAWiaGGm

Blue "button" prints location to console, green one attempts to reverse-geocode an address, yellow one attempts to geocode the test address. The coding probably isn't too great, there may be some leftovers from failed workarounds, I don't have much experience with the scene module yet and I only recently got back into Python. It works as a simple test though.

lenoirmind

Thanks to your script example I found a strange anomaly, dgelessus!
It took my quite a while to figure this out and it is due to similar strange behavior I've seen wehen working in a PHP based project, where I found out that the contents of variables seemed to be updated in a different fashion when FIRST printing the values of these variables to stdout and THEN access the values of these variables to print them elsewhere - eg on a screen

In your code example at http://pastebin.com/ZAWiaGGm you've used this construction:

 location.start_updates()
            self.loc = location.get_location()
            print(self.loc)
            location.stop_updates()

I've modified your code so the draw() method displays the value of the self.loc variable on the canvas as well as in the interpreter. As you've noticed, the interpreter's values are correctly updated. The values within the scene are not.

After some experimentation I've noticed the "print(self.loc))" line in your code and I got this "PHP problem hunch". I've added the same print() line in my Scenes based script and lo and behold! The location values are now correctly updated within the Scene's draw loop!

With your original script with the three buttons you can see the same behavior. If you comment out the 'print(self.loc)' line, you'll notice the values printed on the screen canvas are not updated. As soon as you uncomment the print() line, the values are correctly updated.

I've added my modifications here:

import console
import location

from scene import *

running = True
testaddr = {'Street': 'Infinite Loop', 'City': 'Cupertino', 'Country': 'USA'}
testcoords = (())

def geocode():
testcoords = location.geocode(testaddr)

class MainScene (Scene):
def init(self):
self.btn = 0
self.loc = ''

def setup(self):
    # This will be called before the first frame is drawn.
    pass

def draw(self):
    # This will be called for every frame (typically 60 times per second).
    background(0, 0, 0)

    # draw wannabe buttons
    fill(0, 0, 1)
    rect(0, 0, 200, 200)

            # add continuous polling of location. Not so economic, but only for this testing case
    location.start_updates()
    self.loc = location.get_location()

            # make sure to add this print statement: comment this line to see the update of the self.loc value fail.
    print(self.loc)
    location.stop_updates()

            # if we've successfully obtained our location, display it in the scene in all it's floating number glory
    if self.loc:
        mylocation = '%.9f, %.9f, %d' % (self.loc['longitude'], self.loc['latitude'], self.loc['timestamp'])
        stroke(1,1,1)
        text(mylocation, 'Helvetica', 20, 100, 10, 6)

    # Draw a red circle for every finger that touches the screen:
    fill(1, 0, 0)
    for touch in self.touches.values():
        ellipse(touch.location.x - 50, touch.location.y - 50, 100, 100)

def touch_began(self, touch):
    # determines touched "button"
    if touch.location.x < 200:
        if touch.location.y < 200:
            self.btn = 1
        elif touch.location.y < 400:
            self.btn = 2
        elif touch.location.y < 600:
            self.btn = 3

def touch_moved(self, touch):
    pass

def touch_ended(self, touch):
    stroke(1,1,1)
    # runs the tapped button's corresponding function
    if self.btn == 1:
        text('button 1', 'Helvetica', 20, 100, 200, 6)
        self.dumploc()
        elif self.btn == 2:
        text('button 2', 'Helvetica', 20, 100, 300, 6)
        self.dumpaddr()
    elif self.btn == 3:
        text('button 3', 'Helvetica', 20, 100, 400, 6)
        self.geocode()
    self.btn = 0

def dumploc(self):
    location.start_updates()
    self.loc = location.get_location()
    print(self.loc)
    location.stop_updates()

def dumpaddr(self):
    self.addr = location.reverse_geocode(self.loc)
    print(self.addr)

def geocode(self):
    geocode()
    print(testcoords)

run(MainScene())

Though this perhaps doesn't solve the reverse_geocode issue, it does solve continuous update issue I've ran into.

Not sure I understand why this happens, but I am happy that I found a workaround for the continuous location update issue. If I find a gap in my schedule I will dive into the reverse_geocode issue.

Thanks for your input!

daveM

I’ve stumbled across this problem tonight except it’s just a custom ui.View class and I’m trying to get a new geolocation and reverse location in the view’s update method. The generic lay/long speed and everything else is there, but the geolocation and reverse lookup keep returning None.

The script works perfectly outside a class.

I’m open to ideas because I’ve been scratching my head for hours.

cvp

@daveM could you post the code?

daveM

working code

import console, ui, motion, time
import location

start = time.time()
motion.start_updates()
location.start_updates()

console.set_idle_timer_disabled(True)

def dict_print(dic):
    l = 0
    for k in dic:
        l = max(l,len(k))
    for k,v in dic.items():
        print(f'{k}:'.ljust(l+1),v)

for _ in range(1):

    my_location = location.get_location()
    print('location:')
    dict_print(my_location)


    #address_dict = {'Street': 'Infinite Loop', 'City': 'Cupertino', 'Country': 'USA'}

    my_address = {
        k:v for k, v in my_location.items()} 
    # if k=='latitude' or k=='longitude'}


    print ('my address1:')
    dict_print(my_address)
    my_address = location.geocode(my_location)
    print ('\nmy address2:')
    dict_print(dict(my_address))

    results = location.reverse_geocode(my_location)
    print ('reverse geocode:')
    if results:
        dict_print(results[0])
    print('authorised:',location.is_authorized())

    while False:
        a, b, c = motion.get_attitude()
        # a tilt left right
        # b roll forwards and backwards
        # c twist about horizontal axis
        print('{: .2f}, {: .2f}, {: .2f},'.format(a,b,c))
        time.sleep(.5)

motion.stop_updates()
location.stop_updates()```


# The non working code: 
*you will need to create a pyui file that contains three labels:*
['latlong', 'speed', 'address']

 ```   import console, ui, time
    import location, motion

    #console.set_idle_timer_disabled(True)

    def dict_print(dic):
        l = 0
        for k in dic:
            l = max(l,len(k))
        s = ''
        for k,v in dic.items():
            s += f'{k.ljust(l+1)}: {v}\n'
        return s 

    class myview (ui.View):
        pass
        def __init__(self, *arg, **args):
            start = time.time()
            motion.start_updates()
            location.start_updates()

        def update(self):
            if not location.is_authorized():
                self['address'].text = 'This app needs access to your location data'
                return
            my_location = location.get_location()
            self['latlong'].text = ', '.join([str(my_location['latitude']), str(my_location['longitude'])])

            self['speed'].text = ' / '.join(['{: .2} m/s'.format(my_location['speed']),'{: 0.2} km/h'.format(my_location['speed']*60*60/1000)])

            results = location.geocode(my_location)
            print(results)
            results = location.reverse_geocode(my_location)
            print ('reverse geocode:')
            s=''
            if results:
                self['address'].text = dict_print(results[0])
            #self['address'].text = str(results)
            print(results)

    location.start_updates()
    motion.start_updates()
    v = ui.load_view()
    v.update_interval=1
    print('authorised:',location.is_authorized())
    v.present('sheet')
    motion.stop_updates()
    location.stop_updates()
cvp

@daveM For info and if needed, you can post a .pyui file by
- renaming to .txt
- edit
- select all
-copy
- paste in the forum

daveM

Ahh cool Thanks.

cvp

@daveM I've tri d your program on my iPad WiFi (no gps) and my_location contains latitude and longitude but no address. Thus it is normal that geocode does not work but reverse should work, you're right

cvp

@daveM Tried on my iPhone with gps (I hope 😅) and same result

daveM

Yeah, I’m really not sure what’s happening. I don’t really get it. If you try the code at the very top, it should work as expected and GeoCode and ReverseGeo both work (probably not on your iPad, did you try the working code on your phone?

I only dig up this old thread because the problem seemed to be identical and was hopeful that one of the original posters might remember how (if) they resolved it

cvp

@daveM Just try working code on my iPad and ok (gps known via WiFi router even if no iPad cellular)

daveM

@cvp

Ahh found the issue. I didn’t look hard enough. There was another post detailing the answer and it has worked. Placing the first line here before the update gets things working!!

@ui.in_background
def update(self):
    If not...

https://forum.omz-software.com/topic/5430/reverse-geolocation-works-on-console-but-not-in-code/5

cvp

@daveM well done
the worst is that I knew it, I had read this topic, but I forgot, sad to grow old 👴🏻😢

Only 3 months later, shame on me

daveM

Lol. Yes. I saw your name there. All good. I’m not sure how much older than me you might be, but I certainly understand the pains of a failing memory!!! Lol

cvp

@daveM I never say my age but if you tap on @cvp you will see it 😂

cvp

@daveM Also, be careful, these geocode functions need an Internet connection and if you call them to often, you could be refused during some time...

cvp

@daveM see Apple doc

you should not send more than one geocoding request per minute

Édit: typed before reading your post just under

daveM

Rofl re not speaking your age. Lol. I’ve just hit a milestone that involved the words [‘half’,’century’], but as with most people I know, now it is just a number. Nobody really cares (unless you’re looking for work or a partner!!!) lol

Re the geocode API, I’ve been wondering what the “go” is with it. I have been using the google API for a few years and never had any issues. But I’ve always respected the limits and played by the rules. I was surprised Apple has opened them up to third parties. I’ll dig through the Apple docs later and see what restrictions are in place, but I don’t plan to use it too often. I’m developing an in-house tool that will allow using it as required. But it’ll only be 10-20 times a day. Although I may hit limits in testing. I hope that’s not the case. Lol

daveM

@cvp you beat me to it! Thanks very much I do appreciate that! Cheers!!

(Damn, if someone could hit me some rep points I’d appreciate it!!! @cvp and I submitted a message about the same time, I can’t respond until the time passes... which is why I added this text to the bottom!!) lol

cvp

@daveM see this script

I localize my contacts on a map using this CLGeocoder and I have more than 200 contacts. The script has to retry a lot because it is too much requests in some seconds.

On GitHub, this explanation of my code:

sometimes, geocodeAddressString returns None. Either the address string is invalid, but somerimes, a retry gives a correct gps localization. Perhaps due to a big number of calls in a short time. In this case, the script display a button 'nn not (yet) localized' and starts a thread which will retry (maximum 100 times) all not yet localized contacts. The delay between two retries increase at each retry. The button title is green if the thread runs and red if not. Tapping this button gives the list of these contacts with their retries number

daveM

Nice idea. I do like it. I love that we have such great access to the Apple ecosystem through python. Some of the stuff I’ve seen people put together (yourself included) is encouraging.