gboeing / osmnx

OSMnx is a Python package to easily download, model, analyze, and visualize street networks and other geospatial features from OpenStreetMap.
https://osmnx.readthedocs.io
MIT License
4.82k stars 821 forks source link

The coordinates of the `G.edges()` are incorrect. #911

Closed lead8000 closed 1 year ago

lead8000 commented 1 year ago

Problem description

Environment information

$ pip list absl-py 1.3.0 affine 2.3.1 anyio 3.6.2 appdirs 1.4.4 argon2-cffi 21.3.0 argon2-cffi-bindings 21.2.0 asgiref 3.5.2 astroid 2.11.7 asttokens 2.0.5 astunparse 1.6.3 attrs 22.1.0 autocommand 2.2.2 backcall 0.2.0 beautifulsoup4 4.11.1 black 22.6.0 bleach 5.0.1 blis 0.7.9 branca 0.6.0 btrfsutil 6.0.2 cachetools 5.2.0 catalogue 2.0.8 cbor 1.0.0 celluloid 0.2.0 certifi 2022.9.24 cffi 1.15.1 chardet 5.1.0 click 8.1.3 click-plugins 1.1.1 cligj 0.7.2 colorama 0.4.6 commonmark 0.9.1 confection 0.0.3 contextlib2 21.6.0 contourpy 1.0.6 coverage 6.5.0 cryptography 38.0.4 cvxopt 1.3.0 cycler 0.11.0 cymem 2.0.7 Cython 0.29.32 debugpy 1.6.0 decorator 5.1.1 defusedxml 0.7.1 descartes 1.1.0 dictdatabase 2.4.1 dill 0.3.5.1 distro 1.8.0 Django 4.1.3 dnspython 1.16.0 docutils 0.19 email-validator 1.3.0 en-core-web-sm 3.4.1 entrypoints 0.4 executing 0.8.3 fastapi 0.75.0 fastjsonschema 2.16.2 feedparser 6.0.10 Fiona 1.8.22 Flask 2.2.2 flatbuffers 22.11.23 folium 0.14.0 fonttools 4.33.3 future 0.18.2 gast 0.4.0 GDAL 3.6.1 geopandas 0.12.2 google-auth 2.15.0 google-auth-oauthlib 0.4.6 google-pasta 0.2.0 gpg 1.18.0 greenlet 1.1.2 grpcio 1.51.1 h11 0.14.0 h5py 3.7.0 html2text 2020.1.16 httptools 0.2.0 idna 3.4 igraph 0.10.2 ijson 3.1.4 image 1.5.33 inflect 6.0.2 iniconfig 1.1.1 ipykernel 6.13.0 ipython 8.3.0 ipython-genutils 0.2.0 ipythonblocks 1.9.0 ipywidgets 8.0.2 ir-datasets 0.5.4 isort 5.10.1 itsdangerous 2.1.2 jaraco.context 4.2.0 jaraco.functools 3.5.2 jaraco.text 3.11.0 jarowinkler 1.2.3 jedi 0.18.1 Jinja2 3.1.2 joblib 1.2.0 jsonschema 4.17.3 jupyter 1.0.0 jupyter-client 7.3.0 jupyter-console 6.4.4 jupyter_core 5.1.0 jupyter-server 1.23.3 jupyterlab-pygments 0.2.2 jupyterlab-widgets 3.0.3 keras 2.11.0 kiwisolver 1.4.2 langcodes 3.3.0 lazy-object-proxy 1.7.1 libclang 14.0.6 libcomps 0.1.19 lit 14.0.6.dev0 lxml 4.9.1 lz4 4.0.2 Markdown 3.4.1 MarkupSafe 2.1.1 matplotlib 3.6.2 matplotlib-inline 0.1.3 mccabe 0.7.0 mistune 2.0.4 more-itertools 9.0.0 mpmath 1.2.1 msal 1.18.0 msgpack 1.0.4 munch 2.5.0 murmurhash 1.0.9 mypy 0.971 mypy-extensions 0.4.3 nbclassic 0.4.8 nbclient 0.7.2 nbconvert 7.2.5 nbformat 5.7.0 neovim 0.3.1 nest-asyncio 1.5.5 networkx 2.8.8 notebook 6.5.2 notebook_shim 0.2.2 nspektr 0.4.0 numpy 1.24.0 nyx 2.1.0 oauthlib 3.2.2 opencv-python 4.6.0.66 opt-einsum 3.3.0 ordered-set 4.1.0 orjson 3.8.2 osmnx 1.2.3 packaging 21.3 pandas 1.5.2 pandocfilters 1.5.0 parso 0.8.3 pathspec 0.9.0 pathy 0.10.0 pep517 0.13.0 pexpect 4.8.0 pickleshare 0.7.5 Pillow 9.1.0 pip 22.3.1 platformdirs 2.5.4 pluggy 1.0.0 ply 3.11 preshed 3.0.8 progress 1.6 prometheus-client 0.15.0 prompt-toolkit 3.0.29 protobuf 3.19.6 psutil 5.9.0 ptyprocess 0.7.0 pure-eval 0.2.2 py 1.11.0 pyasn1 0.4.8 pyasn1-modules 0.2.8 pyautocorpus 0.1.9 pycparser 2.21 pydantic 1.9.0 pygame 2.1.2 Pygments 2.12.0 PyJWT 2.4.0 pylint 2.14.5 pymongo 3.11.0 PyNaCl 1.4.0 pynvim 0.4.3 pyOpenSSL 22.1.0 pyparsing 3.0.8 pyproj 3.4.1 PyQt5 5.15.7 PyQt5-sip 12.11.0 pyrsistent 0.19.2 PySocks 1.7.1 pytest 7.0.1 pytest-cov 4.0.0 python-dateutil 2.8.2 python-dotenv 0.19.2 python-multipart 0.0.5 pytz 2022.6 PyYAML 5.4.1 pyzmq 22.3.0 qpsolvers 2.6.0 qtconsole 5.4.0 QtPy 2.3.0 radian 0.6.3 ranger-fm 1.9.3 rapidfuzz 2.13.5 rasterio 1.3.4 rchitect 0.3.36 realpython-reader 1.1.1 /home/leandro/study/templates/reader-master requests 2.28.1 requests-oauthlib 1.3.1 resolvelib 0.9.0 retrying 1.3.3 rich 12.6.0 rpm 4.18.0 rsa 4.9 Rtree 1.0.1 scikit-learn 1.2.0 scipy 1.8.0 seaborn 0.12.1 Send2Trash 1.8.0 setuptools 62.1.0 sgmllib3k 1.0.0 Shapely 1.8.5.post1 six 1.16.0 sklearn 0.0.post1 smart-open 5.2.1 sniffio 1.3.0 snuggs 1.4.7 sortedcontainers 2.4.0 soupsieve 2.3.2.post1 spacy 3.4.3 spacy-legacy 3.0.10 spacy-loggers 1.0.3 sqlparse 0.4.3 srsly 2.4.5 stack-data 0.2.0 starlette 0.17.1 stem 1.8.0 sympy 1.11.1 team 1.0 tenacity 8.1.0 tensorboard 2.11.0 tensorboard-data-server 0.6.1 tensorboard-plugin-wit 1.8.1 tensorflow 2.11.0 tensorflow-estimator 2.11.0 tensorflow-io-gcs-filesystem 0.28.0 termcolor 2.1.1 terminado 0.17.0 texttable 1.6.7 thinc 8.1.5 threadpoolctl 3.1.0 tinycss2 1.2.1 toml 0.10.2 tomli 2.0.1 tomlkit 0.11.1 torbrowser-launcher 0.3.5 tornado 6.1 tqdm 4.64.1 traitlets 5.6.0 trec-car-tools 2.6 trove-classifiers 2022.12.1 typer 0.7.0 typing 3.7.4.3 typing_extensions 4.4.0 ujson 4.3.0 Unidecode 1.3.6 unlzw3 0.2.1 urllib3 1.26.12 uvicorn 0.15.0 uvloop 0.17.0 validate-pyproject 0.10.1 warc3-wet 0.2.3 warc3-wet-clueweb09 0.2.5 wasabi 0.10.1 watchfiles 0.18.0 watchgod 0.8.2 wcwidth 0.2.5 webencodings 0.5.1 websocket-client 1.4.2 websockets 10.4 Werkzeug 2.2.2 wheel 0.38.4 widgetsnbextension 4.0.3 wrapt 1.14.1 youtube-dl 2021.12.17 zlib-state 0.1.5 $ conda list - I don't have installed conda.

Code

This is the code I use to plot with pygame. The only thing I do is to paint the streets with the exact values provided by the G.edges() function.

import osmnx as ox
address_name='Ciudad Deportiva, Havana, Cuba'

#Import graph
point = (23.1021, -82.3936)
G = ox.graph_from_point(point, dist=1000, retain_all=True, simplify=True, network_type='drive')
G = ox.project_graph(G)

import pygame
from map import Map 

RED = (255, 0, 0)
BLUE = (0, 0, 20)
GRAY = (215,215,215)
WHITE = (255, 255, 255)

inc = 0.8

m = Map(1400, 800, lng=inc*2555299.469922482, lat=inc*356731.10053785384, i_zoom=0.1)

last_x = -1
last_y = -1
i=0
while True:

    for event in pygame.event.get():

        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 4:
                m.inc_zoom()
            elif event.button == 5: 
                m.dec_zoom()

        if event.type == pygame.MOUSEMOTION:
            if event.buttons[0] == 1: 
                if last_x == last_y == -1:
                    last_x, last_y = event.pos
                else:
                    m.x += (event.pos[0] - last_x)/2
                    m.y += (event.pos[1] - last_y)/2
                    last_x, last_y = event.pos

        if event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1:
                last_x = last_y = -1

        m.fill(GRAY)

        # Here I take the coordinates of the streets and paint them
        for _, _, data in G.edges(data=True):
            try:
                m.draw_road(data['geometry'], inc, BLUE)
            except:
                pass

        m.update()

This is the code of map.py.

from typing import List, Tuple
from window import Window
from pygame import gfxdraw
from shapely.geometry import LineString

def build_rect( 
        start: Tuple[float,float], 
        end: Tuple[float,float],
        width: float = 1
    ) -> List[Tuple[float,float]]:

    x0, y0 = start
    x1, y1 = end

    if x0**2 + y0**2 > x1**2 + y1**2:
        x0, y0 = end
        x1, y1 = start

    # vector from start to end
    vX, vY = x1 - x0, y1 - y0   
    # normal vector
    nX, nY = -vY, vX

    # normalize
    n = (nX**2 + nY**2)**0.5
    nX, nY = width/n * nX, width/n * nY

    # third vector
    x2, y2 = x1 + nX, y1 + nY
    # fourth vector
    x3, y3 = x0 + nX, y0 + nY

    return [(x0, y0), (x1, y1), (x2, y2), (x3, y3)]

class Map(Window):

    def __init__(self, width, height, **kwargs):
        super().__init__(width, height, **kwargs) 

    def draw_road(self, st: LineString, inc: float, color: tuple):
        last = None
        for c in st.__geo_interface__['coordinates']:
            c = (inc*c[0], inc*c[1])
            if last == None:
                last = (c[0] - self.lat,c[1] - self.lng)
                continue

            lat = c[0] - self.lat
            lng = c[1] - self.lng

            pts = build_rect(last, (lat, lng))

            gfxdraw.filled_polygon(self.screen, [
                (self.x + lat * self.zoom, self.y + lng * self.zoom)
                for lat, lng in pts
            ], color)

And this is the code of window.py.

import pygame
from pygame import gfxdraw
from pygame.locals import *

class Window:

    def __init__(self, width, height, **kwargs):
        self.width = width
        self.height = height

        self.zoom = 1
        self.x = self.y = 0
        self.i_zoom = 0.001

        self.__dict__.update(kwargs)

        pygame.init()
        self.screen = pygame.display.set_mode((width, height))

    def inc_zoom(self):
        self.zoom += self.i_zoom

    def dec_zoom(self):
        self.zoom -= self.i_zoom
        self.zoom = max(0, self.zoom)

    def draw_polygon(self, points, color):
        gfxdraw.filled_polygon(self.screen, [
            (self.x + pt[0] *  self.zoom, self.y + pt[1] * self.zoom)
            for pt in points], color)

    def fill(self, color):
        self.screen.fill(color)

    def update(self):
        pygame.display.update()
gboeing commented 1 year ago

Thanks for using OSMnx! Please re-read the contributing guidelines and the new issue template you saw when you opened this issue. As the template noted, your issue will be automatically closed if you didn't provide all of the information requested in the template, because we need this information to be able to troubleshoot your issue.

lead8000 commented 1 year ago

First of all, thank you @gboeing for your quick response to my problem, and secondly, happy holidays. I have solved the template problem. My initial question was a how-to question, but I have realized that it is more of a package issue, because if curated data is not provided, it would have to be clarified somewhere in the documentation or examples. Here I can show you the result of plotting using the data provided by G.edges(), and here the result of plotting using the osmnx.plot_graph() function of the package.

gboeing commented 1 year ago

This problem with your code does not appear to have anything to do with OSMnx itself. The coordinates of the edges are correct and precise per what's on OSM. If you provide a couple of OSM ways and nodes whose coordinates do not match what OSMnx says they should be, we can investigate further. Otherwise this just appears to be an issue with how you're plotting with pygame.

lead8000 commented 1 year ago

My way of painting may not be the best or the clearest, but I simply take each edge provided by OSMnx and draw a line. If you look closely at the images I showed you earlier (my painting and its painting) you will notice that there is something strange. Maybe it makes sense because they are consistent when painting the streets with osmnx.plot_graph(), but that is not clear from the documentation or the examples. @gboeing my argument is that I can draw the lines wrong, but I do not invent additional lines, because each line corresponds to an edge provided by G.edges().

gboeing commented 1 year ago

I do not use or support the pygame package so I cannot speak to it. If you have a specific question about drawing with pygame, you'll have to ask its developers or user community. Regarding OSMnx, if you believe the problem is with incorrect coordinates, like I mentioned earlier, please provide us with the IDs of specific OSM ways and nodes whose coordinates do not match what OSMnx has in its graph, so we can investigate further. But, aside from pygame, you can plot the graph's edges with or without OSMnx, and you can see it is correct:

import osmnx as ox
point = (23.1021, -82.3936)
G = ox.graph_from_point(point, dist=1000, retain_all=True, simplify=True, network_type='drive')
G = ox.project_graph(G)

# plot with OSMnx directly
fig, ax = ox.plot_graph(G)

# plot the edges with GeoPandas/matplotlib
edges = ox.graph_to_gdfs(G, nodes=False)
ax = edges.plot()
lead8000 commented 1 year ago

@gboeing thank you very much for your support.

I am doing an academic project and this mapping thing has taken me a long time, at another time maybe I will take it up again and incorporate it into a feature later and then I will be able to tell you with better certainty where is the "inconsistency" I find, maybe there isn't and what I need is a little more information that I didn't find in the documentation.

Happy new year! :partying_face: :tada: