Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

center_on does not center, plus once more the random zoom problem #33

Open
michaelu123 opened this issue May 6, 2020 · 4 comments
Open

Comments

@michaelu123
Copy link

Two bugs in mapview

The following program demonstrates two bugs in mapview.
My mapview version is 0.104.
Python is at 3.7.

from kivy.base import runTouchApp
from kivy.lang import Builder

if __name__ == '__main__' and __package__ is None:
    from os import sys, path

    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

root = Builder.load_string(
    """
#:import MapSource kivy_garden.mapview.MapSource

<Toolbar@BoxLayout>:
    size_hint_y: None
    height: '48dp'
    padding: '4dp'
    spacing: '4dp'

    canvas:
        Color:
            rgba: .2, .2, .2, .6
        Rectangle:
            pos: self.pos
            size: self.size

<ShadedLabel@Label>:
    size: self.texture_size
    canvas.before:
        Color:
            rgba: .2, .2, .2, .6
        Rectangle:
            pos: self.pos
            size: self.size

RelativeLayout:

    MapView:
        id: mapview
        lat: 48.13724404
        lon: 11.57617109
        zoom: 16
        snap_to_zoom: 1
        canvas:
            Color: 
                rgba:1,0,0,0.5
            Line:
                width:1
                points: [self.width/2,0, self.width/2,self.height]
            Line:
                width:1
                points: [0, self.height/2, self.width, self.height/2]
        
        #size_hint: 0.5, 0.5
        #pos_hint: {"x": .25, "y": .25}

        # next line enabled causes infinite loop
        #on_map_relocated: mapview.sync_to(self)

        MapMarker: # Fischbrunnen
            #size: 40,40
            lat: 48.13724404
            lon: 11.57617109
            on_press: mapview.center_on(self.lat, self.lon)

    Toolbar:
        top: root.top
        Button:
            text: "Center"
            on_release: mapview.center_on(48.13724404, 11.57617109)
        Spinner:
            text: "mapnik"
            values: MapSource.providers.keys()
            on_text: mapview.map_source = self.text

    Toolbar:
        Label:
            text: "Zoom: {}".format(mapview.zoom)
        Label:
            text: "Longitude: {}".format(mapview.lon)
        Label:
            text: "Latitude: {}".format(mapview.lat)
    """
)

root.ids.mapview.map_source.bounds = (11.5, 48.1, 11.6, 48.2)
root.ids.mapview.map_source.min_zoom = 15
runTouchApp(root)

The first bug is a random zoom, when the map is moved in high zoom levels.
It is also issued in #22 .
This one is easy to fix. I noticed that the zoom changed when scale was
a little bit larger than 2.0, e.g. 2.00000004. In view.py/on_transform replace

        if scale >= 2.0:
            zoom += 1
            scale /= 2.0
        elif scale < 1:
            zoom -= 1
            scale *= 2.0

with

        if scale >= 2.01:
            zoom += 1
            scale /= 2.0
        elif scale < 0.99:
            zoom -= 1
            scale *= 2.0

The second bug I was unable to fix, because the interaction
between Scatter and Mapview remains a mystery for me. The problem shows
normally in high zoom level, and it occurs randomly. When you press the
"Center" button, the map moves consistently to the "Fischbrunnen" at
Marienplatz, Munich. When you however move the map, so that the marker is
outside of the center, and click on the marker, the marker does often not move
back to the center. This may require a few attempts to reproduce. It is easiest
to reproduce when you zoom to level 19, click the Center Button, move the map
so that the marker is near the upper right corner, and click it. Often, the
map moves erratically and sometimes zooms out. This behaviour seems to be
dependent on the mouse position when the marker is clicked. When we center
the map with the "Center"-Button, the mouse is outside of the Scatter,
and this seems to be the reason why this center always succeeds.

In the error case,
do_update gets called many times, where the scale grows slowly from 1 to 2.
Maybe floating point precision plays a role here. But then there is the random
behaviour of the bug, which may have to do with the unforeseeable times,
in which the Clock calls the scheduled callbacks.

Workaround technique

I could easily change the code in the package, when running my program on the desktop.
On Android, however, I could not find out what must be done, to change
the code in one of the required packages, Below .buildozer, there are a
couple of directories containing mapview code, plus some zip files, plus
some .pyc files, and nothing I did caused the app to run the modified code.
Here I describe a workaround:

In main.py, between the App construction and app.run, call bugs.fixBugs()

    app = MyApp()
    import bugs
    bugs.fixBugs()
    app.run()

bugs.py contains this code:

from kivy.uix.widget import Widget

def clamp(x, minimum, maximum):
    y = max(minimum, min(x, maximum))
    return y

class MapView(Widget):
    def on_transform(self, *args):
        self._invalid_scale = True
        if self._transform_lock:
            return
        self._transform_lock = True
        # recalculate viewport
        map_source = self.map_source
        zoom = self._zoom
        scatter = self._scatter
        scale = scatter.scale
        #MUHif scale >= 2.0:
        if scale > 2.01:
            zoom += 1
            scale /= 2.0
        #MUH elif scale < 1.0:
        elif scale < 0.99:
            zoom -= 1
            scale *= 2.0
        zoom = clamp(zoom, map_source.min_zoom, map_source.max_zoom)
        if zoom != self._zoom:
            self.set_zoom_at(zoom, scatter.x, scatter.y, scale=scale)
            self.trigger_update(True)
        else:
            if zoom == map_source.min_zoom and scatter.scale < 1.0:
                scatter.scale = 1.0
                self.trigger_update(True)
            else:
                self.trigger_update(False)

        if map_source.bounds:
            self._apply_bounds()
        self._transform_lock = False
        self._scale = self._scatter.scale

def fixBugs():
    import kivy_garden.mapview as mv
    mv.MapView.on_transform = MapView.on_transform

@michaelu123
Copy link
Author

After some more hours of poking around, I first had to realize that the random zoom had not been entirely defeated, as I had thought.
In the end, I substituted in all comparisons of floats with a constant the float with its rounded value.
bugs.py now reads:

import kivy.weakmethod as wm
import kivy_garden.mapview as mv
from kivy.uix.widget import Widget
class MapView(Widget):
    def on_touch_up(self, touch):
        if touch.grab_current == self:
            touch.ungrab(self)
            self._touch_count -= 1
            if self._touch_count == 0:
                # animate to the closest zoom
                zoom, scale = self._touch_zoom
                cur_zoom = self.zoom
                cur_scale = self._scale
                if cur_zoom < zoom or round(cur_scale, 2) < scale:
                    self.animated_diff_scale_at(1.0 - cur_scale, *touch.pos)
                elif cur_zoom > zoom or round(cur_scale, 2) > scale:
                    self.animated_diff_scale_at(2.0 - cur_scale, *touch.pos)
                self._pause = False
            return True
        return super(mv.MapView, self).on_touch_up(touch)

    def on_transform(self, *args):
        self._invalid_scale = True
        if self._transform_lock:
            return
        self._transform_lock = True
        # recalculate viewport
        map_source = self.map_source
        zoom = self._zoom
        scatter = self._scatter
        scale = scatter.scale
        if round(scale, 2) >= 2.0:
            zoom += 1
            scale /= 2.0
        elif round(scale, 2) < 1.0:
            zoom -= 1
            scale *= 2.0
        zoom = clamp(zoom, map_source.min_zoom, map_source.max_zoom)
        if zoom != self._zoom:
            self.set_zoom_at(zoom, scatter.x, scatter.y, scale=scale)
            self.trigger_update(True)
        else:
            if zoom == map_source.min_zoom and round(scatter.scale, 2) < 1.0:
                scatter.scale = 1.0
                self.trigger_update(True)
            else:
                self.trigger_update(False)

        if map_source.bounds:
            self._apply_bounds()
        self._transform_lock = False
        self._scale = self._scatter.scale

    @property
    def scale(self):
        if self._invalid_scale:
            self._invalid_scale = False
            self._scale = round(self._scatter.scale, 2)
        return self._scale


def fixBugs():
    mv.MapView.on_touch_up = MapView.on_touch_up
    mv.MapView.on_transform = MapView.on_transform
    mv.MapView.scale = MapView.scale

Note the various calls to round(). Interestingly, I had to replace in on_touch_up the final call of

return super().on_touch_up(touch)

with

return super(mv.MapView, self).on_touch_up(touch)

otherwise the super()-call failed with the message
TypeError: super(type, obj): obj must be an instance or subtype of type

The whole rounding is certainly a hack, but apparently mapview needs some more consideration for floating
point accuracy. I hope someone who knows what he does (as opposed to me, I am just guessing,
using trial and error) eventually comes up with a clean solution.

@michaelu123
Copy link
Author

Closed involuntarily, not meaning to do so...

@michaelu123 michaelu123 reopened this May 21, 2020
@bsuarez49
Copy link

@michaelu123
Thanks for the resolution track.
Your solution works almost perfectly, it still persists some random zoom when moving the map.

After several tests, I commented out the following 2 lines self.animated_diff_scale_at(.....) in

def on_touch_up(self, touch):
...
      #self.animated_diff_scale_at(1.0 - cur_scale, *touch.pos)
....
      #self.animated_diff_scale_at(1.0 - cur_scale, *touch.pos)

This removes all random zooms!

@RishabhM-rish
Copy link

@michaelu123 @bsuarez49 , thank you guys , you really helped it, now the problem is gone

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants