diff --git a/.gitignore b/.gitignore index 922918e..7f15977 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ __pycache__/ *.py[cod] +# Ctags +tags + # C extensions *.so diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..daa7862 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include pytilemap/*.png diff --git a/README.md b/README.md index f5d2dae..643355e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,36 @@ # PyTileMap -Tile map visualization for PyQt4. +PyQt Map System forked from: -This project allows the visualization of Open Street Map, HERE and other maps -in a PyQt4 GUI. +https://github.com/allebacco/PyTileMap -The project has been inspired from of https://github.com/emka/python-lightmaps. +Simple to use map widget that can be used in a PyQt GUI. + +# Install: +python setup.py install + +# Example: + +examples/main_gs.py + +# Map Widgets + +- MapGraphicsLineItem - a line between two lat/lon points +- MapGraphicsPolyLineItem - lines between a series of lat/lon points +- MapGraphicsPixmapItem - place a pixmap at a given lat/lon (does not scale with map zoom) +- MapGraphicsTextItem - place text at a given lat/lon +- MapGraphicsLinesGroupItem - a group of lines +- MapGraphicsCircleItem - draw a circle centered at a lat/lon +- MapGraphicsRectItem - draw a rectangle given upper left and lower right lat/lon (scales with zoom) +- MapGraphicsRectShapeItem - draw a rectangle with a fixed with and height (does not scale with zoom) +- MapGraphicsGeoSvgItem - draw an svg given upper left and lower right lat/lon (scales with zoom) +- MapGraphicsGeoPixmapItem - draw a pixmap given upper left and lower right lat/lon (scales with zoom) +- MapGraphicsGeoPixmapItemCorners - draw a pixmap given all four lat/lon corners (scales with zoom) +- MapGraphicsLabelItem - a label that is attached to another map item + +# Screenshot +![Screenshot](screenshot.png) + +# Other Python Maps + +https://github.com/TomSchimansky/TkinterMapView diff --git a/example/main_gs.py b/example/main_gs.py index 33904d5..051aa1a 100644 --- a/example/main_gs.py +++ b/example/main_gs.py @@ -8,6 +8,7 @@ from pytilemap import MapGraphicsView, MapTileSourceHere, MapTileSourceOSM + POINTS = [(44.837632, 10.201736), (44.837621, 10.201474), (44.837594, 10.201205), @@ -77,7 +78,7 @@ def __init__(self): self.setCentralWidget(view) - view.scene().setCenter(10.065990, 44.861041) + view.scene().setCenter(10.065990, 44.861041, zoom=13) view.setOptimizationFlag(QGraphicsView.DontSavePainterState, True) view.setRenderHint(QPainter.Antialiasing, True) view.setRenderHint(QPainter.SmoothPixmapTransform, True) @@ -111,13 +112,46 @@ def __init__(self): pointItemPixmapOrigin = view.scene().addCircle(10.090598, 44.857893, 3.0) pointItemPixmapOrigin.setBrush(Qt.black) - pointItemWithChild = view.scene().addCircle(10.083103, 44.858014, 3.0) - pointItemWithChild.setBrush(Qt.blue) - pointItemWithChild.setPen(QPen(Qt.NoPen)) + # Pixmap with both corners geo-referenced + geo_pixmap = QPixmap(36,36) + geo_pixmap.fill(Qt.blue) + geo_pixmap_item= view.scene().addGeoPixmap(10.090598, 44.8709, 10.092, 44.873, geo_pixmap) + geo_pixmap_item.setLabel("GeoPixmapItem") + geo_pixmap_item.showLabel() + + # Blue Point with an HTML label + blue_point = view.scene().addCircle(10.083103, 44.868014, 3.0) + blue_point.setBrush(Qt.blue) + blue_point.setPen(QPen(Qt.NoPen)) + blue_point.setLabel("
" + "Label for Blue Point" + "
", html=True) + blue_point.showLabel() + + # Location Pin + pin_item = view.scene().addPin(10.06, 44.84) + pin_item.setLabel("
Pin Item
",html=True) + pin_item.showLabel() + + # Pixmap with all four corners geo-referenced + lon0r = 10.06 + lat0r = 44.83 + lon1r = 10.110000000000001 + lat1r = 44.743397459621555 + lon2r = 9.936794919243113 + lat2r = 44.64339745962155 + lon3r = 9.886794919243112 + lat3r = 44.73 + + clr = QColor(0,255,0,100) + pix = QPixmap(100,100) + pix.fill(clr) + + view.scene().addGeoPixmapCorners(lon0r, lat0r, + lon1r, lat1r, + lon2r, lat2r, + lon3r, lat3r, + pix) + - textItem = QGraphicsSimpleTextItem('Annotation\nfor blue point', parent=pointItemWithChild) - textItem.setBrush(QBrush(QColor(Qt.blue))) - textItem.setPos(-5, 3) lats_2 = list() lons_2 = list() @@ -134,8 +168,12 @@ def __init__(self): legendItem.addRect('Sphere 4', '#00FFFF', border=None) legendItem.addPoint('Polygon 5', '#FF00FF', border=None) + navItem = view.scene().addNavItem(anchor=Qt.TopRightCorner) + scaleItem = view.scene().addScale(anchor=Qt.BottomRightCorner) + + def main(): w = MapZoom() diff --git a/pytilemap/__init__.py b/pytilemap/__init__.py index 133b0cc..42a075f 100644 --- a/pytilemap/__init__.py +++ b/pytilemap/__init__.py @@ -2,13 +2,15 @@ from .mapscene import MapGraphicsScene from .mapview import MapGraphicsView -from .mapitems import MapGraphicsCircleItem, MapGraphicsLineItem, \ +from .mapitems import MapItem, MapGraphicsCircleItem, MapGraphicsLineItem, \ MapGraphicsPolylineItem, MapGraphicsPixmapItem, MapGraphicsTextItem, \ MapGraphicsRectItem from .maplegenditem import MapLegendItem from .mapescaleitem import MapScaleItem from .maptilesources import MapTileSource, MapTileSourceHere, MapTileSourceHereDemo, \ MapTileSourceOSM, MapTileSourceHTTP +from .mapnavitem import MapNavItem +from .imagebutton import ImageButton __all__ = [ @@ -26,6 +28,9 @@ 'MapTileSourceHereDemo', 'MapTileSourceOSM', 'MapTileSourceHTTP', + "MapNavItem", + "ImageButton", + "MapItem" ] __version__ = '1.0.0' diff --git a/pytilemap/imagebutton.py b/pytilemap/imagebutton.py new file mode 100644 index 0000000..ffd1803 --- /dev/null +++ b/pytilemap/imagebutton.py @@ -0,0 +1,39 @@ +from __future__ import print_function, absolute_import + +from qtpy.QtCore import Qt, Signal +from qtpy.QtWidgets import QGraphicsObject, QGraphicsItemGroup, QGraphicsPixmapItem + +class ImageButton(QGraphicsObject): + ''' + Custom Image Holder Class that Allows for event handling - + - QGraphicsPixmapItem is not a QObject, so cannot catch events. + ''' + QtParentClass = QGraphicsObject + + clicked = Signal(int) + + def __init__(self, img, parent=None): + ''' + Args: + img: A QPixmap instance + + Keyword Args: + parent: A pyqt window instance + ''' + QGraphicsObject.__init__(self, parent=parent) + self.image = QGraphicsPixmapItem(img, parent=self) + self.parent = parent + + def paint(self, painter, option, widget): + self.image.paint(painter,option,widget) + + def boundingRect(self): + return self.image.boundingRect() + + def mouseReleaseEvent(self, event): + self.clicked.emit(1) + + def mousePressEvent(self, event): + pass +# end ImageButton + diff --git a/pytilemap/mapitems.py b/pytilemap/mapitems.py index 581bda9..b949845 100644 --- a/pytilemap/mapitems.py +++ b/pytilemap/mapitems.py @@ -2,11 +2,14 @@ import numpy as np -from qtpy.QtCore import Qt, QLineF, QPointF, QRectF -from qtpy.QtGui import QPainterPath +from qtpy.QtCore import Qt, QLineF, QPointF, QRectF, QSize +from qtpy.QtGui import QPainterPath, QPen, QBrush, QColor, QTransform, QPolygonF from qtpy.QtWidgets import QGraphicsEllipseItem, QGraphicsLineItem, \ QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsItemGroup, \ - QGraphicsSimpleTextItem, QGraphicsItem, QGraphicsRectItem + QGraphicsSimpleTextItem, QGraphicsItem, QGraphicsRectItem, QGraphicsTextItem, QMenu, QAction + +#from qtpy.QtWidgets import QGraphicsSvgItem +from qtpy.QtSvg import QGraphicsSvgItem, QSvgRenderer from .functions import iterRange, makePen, izip from .qtsupport import getQVariantValue @@ -24,10 +27,15 @@ class MapItem(object): QtParentClass = None + def __init__(self): if not isinstance(self, QGraphicsItem): raise RuntimeError('MapItem must be an instance of QGraphicsItem') + self._label = "label" + self._label_item = None + self._label_html = False + def itemChange(self, change, value): if change == self.ItemSceneChange: # Disconnect the old scene, if any @@ -79,6 +87,52 @@ def setZoom(self, zoom): def updatePosition(self, scene): raise NotImplementedError() + def setLabel(self, label, html=False): + self._label = label + self._label_html = html + + def getLabelLocation(self): + ''' Get label location for this object + + Args: + none + + Returns: + (pos x, pos y) : position of label in pixels + ''' + rect = self.getGeoRect() + br = rect.bottomRight() + pos = (br.x(), br.y()) + return pos + + def getGeoRect(self): + ''' Get bounding rectangle for this obj + + Args: + none + + Returns: + QRectF: (px x, px y, w, h) + ''' + return self.boundingRect() + + def showLabel(self): + ''' Show label for this object, if html is indicated, display formatted''' + if self._label_item: + return + self._label_item = MapGraphicsLabelItem(self, self._label) + if self._label_html: + self._label_item.setHtml(self._label) + self.scene().addItem(self._label_item) + + def hideLabel(self): + ''' Hide label for this object''' + if not self._label_item: + return + self.scene().removeItem(self._label_item) + self._label_item = None + + class MapGraphicsCircleItem(QGraphicsEllipseItem, MapItem): """Circle item for the MapGraphicsScene @@ -101,9 +155,10 @@ def __init__(self, longitude, latitude, radius, parent=None): """ QGraphicsEllipseItem.__init__(self, parent=parent) MapItem.__init__(self) + self.setFlags(QGraphicsItem.ItemIsMovable) - self._lon = longitude - self._lat = latitude + self._lon = longitude + self._lat = latitude self._radius = radius def updatePosition(self, scene): @@ -130,6 +185,8 @@ def setLonLat(self, longitude, latitude): scene = self.scene() if scene is not None: self.updatePosition(scene) + if self._label_item: + self._label_item.updatePosition(scene) def setRadius(self, radius): self._radius = radius @@ -137,6 +194,58 @@ def setRadius(self, radius): if scene is not None: self.updatePosition(scene) + def hideLabel(self): + if not self._label_item: + return + self.scene().removeItem(self._label_item) + self._label_item = None + +class MapGraphicsRectShapeItem(QGraphicsRectItem, MapItem): + """Circle item for the MapGraphicsScene + """ + + QtParentClass = QGraphicsRectItem + + def __init__(self, lon, lat, width, height, parent=None): + """Constructor. + + Args: + lon0(float): Longitude of the center point + lat0(float): Latitude of the center point + width(int): width in pixels + height(int): height in pixels + parent(QGraphicsItem): Parent item, default None. + + """ + QGraphicsRectItem.__init__(self, parent=parent) + MapItem.__init__(self) + + self._lon = lon + self._lat = lat + self._width = width + self._height = height + + def updatePosition(self, scene): + """Update the position of the circle. + + Args: + scene(MapGraphicsScene): Scene to which the circle belongs. + """ + pos = scene.posFromLonLat(self._lon, self._lat) + + self.prepareGeometryChange() + # This object is centered on the lat lon point, so shift it by half width/height + rect = QRectF(pos[0]-self._height//2, pos[1]-self._width//2, self._width, self._height) + self.setRect(rect) + self.setPos(QPointF(0.0, 0.0)) + + def setLonLat(self, lon, lat): + self._lon = lon + self._lat = lat + scene = self.scene() + if scene is not None: + self.updatePosition(self.scene()) + class MapGraphicsRectItem(QGraphicsRectItem, MapItem): """Circle item for the MapGraphicsScene @@ -173,9 +282,11 @@ def updatePosition(self, scene): """ pos0 = scene.posFromLonLat(self._lon0, self._lat0) pos1 = scene.posFromLonLat(self._lon1, self._lat1) + width = abs(int(pos1[0] - pos0[0])) + height= abs(int(pos0[1] - pos1[1])) self.prepareGeometryChange() - rect = QRectF(pos0, pos1).normalized() + rect = QRectF(pos0[0], pos0[1], width, height) self.setRect(rect) self.setPos(QPointF(0.0, 0.0)) @@ -221,6 +332,7 @@ def setLonLat(self, lon0, lat0, lon1, lat1): self.updatePosition(self.scene()) + class MapGraphicsPolylineItem(QGraphicsPathItem, MapItem): QtParentClass = QGraphicsPathItem @@ -257,6 +369,307 @@ def setLonLat(self, longitudes, latitudes): if scene is not None: self.updatePosition(scene) +class MapGraphicsGeoSvgItem(QGraphicsSvgItem, MapItem): + + QtParentClass = QGraphicsSvgItem + + def __init__(self, lon0, lat0, lon1, lat1, svg_filename, parent=None): + """Constructor. + + Args: + longitude(float): Longitude of the upper left corner + latitude(float): Latitude of the upper left corner + longitude(float): Longitude of the lower right corner + latitude(float): Latitude of the lower right corner + svg_filename: Svg file name + scene(MapGraphicsScene): Scene the item belongs to. + parent(QGraphicsItem): Parent item. + + This will display an svg file with the corners geo-registered + """ + QGraphicsSvgItem.__init__(self, svg_filename, parent=parent) + MapItem.__init__(self) + + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + self._xsize = 0 + self._ysize = 0 + + self.x_mult = 1 + self.y_mult = 1 + self._renderer = QSvgRenderer(svg_filename); + self._border = QGraphicsRectItem(parent=self) + self._border.setPen(Qt.black) + + def updatePosition(self, scene): + pos0 = scene.posFromLonLat(self._lon0, self._lat0) + pos1 = scene.posFromLonLat(self._lon1, self._lat1) + self.prepareGeometryChange() + xsize = abs(int(pos1[0] - pos0[0])) + ysize = abs(int(pos0[1] - pos1[1])) + + rect = scene.sceneRect() + x = rect.x() + y = rect.y() + width = rect.width() + height = rect.height() + self.ul_x = min(pos0[0], pos1[0]) + self.ul_y = min(pos0[1], pos1[1]) + self.lr_x = max(pos0[0], pos1[0]) + self.lr_y = max(pos0[1], pos1[1]) + #self.scale(width, height) + + #print ("screen rect: {0}:{1}, {2}:{3}".format(int(x), int(x+width), int(y), int(y+height)), + # "img rect: {0}:{1}, {2}:{3}".format(int(self.ul_x), int(self.lr_x), int(self.ul_y), int(self.lr_y))) + + #if xsize != self._xsize or ysize != self._ysize: + self._xsize = xsize + self._ysize = ysize + self.ul_x = min(pos0[0], pos1[0]) + self.ul_y = min(pos0[1], pos1[1]) + self.setPos(self.ul_x, self.ul_y) + + # Scaled approach - does weird smoothing + def paint(self, painter, option, widget=None): + #print (self.x_mult, self.y_mult, self.orig_pixmap.width(), self.orig_pixmap.height()) + self._renderer.render(painter, QRectF(0,0, self._xsize, self._ysize)) + + def boundingRect(self): + return QRectF(0, 0, self._xsize, self._ysize) + + def getGeoRect(self): + ''' get geo referenced rectangle for this object + + Returns: + QRectF (upper left x, upper left y, width, height) + ''' + pos0 = self.scene().posFromLonLat(self._lon0, self._lat0) + pos1 = self.scene().posFromLonLat(self._lon1, self._lat1) + xsize = abs(int(pos1[0] - pos0[0])) + ysize = abs(int(pos0[1] - pos1[1])) + ul_x = min(pos0[0], pos1[0]) + ul_y = min(pos0[1], pos1[1]) + rect = QRectF(ul_x, ul_y, xsize, ysize) + return rect + + + def setLonLat(self, lon0, lat0, lon1, lat1): + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + scene = self.scene() + if scene is not None: + self.updatePosition(self.scene()) + +# end MapGraphicsGeoSvg + +class MapGraphicsGeoPixmapItemCorners(QGraphicsPixmapItem, MapItem): + ''' + A pixmap that has all 4 corners specified so it warps to the map + ''' + + QtParentClass = QGraphicsPixmapItem + + def __init__(self, lon0, lat0, lon1, lat1, + lon2, lat2, lon3, lat3, pixmap, parent=None): + """Constructor. + + Args: + lon0(float): Longitude (decimal degrees) of the upper left corner of the image + lat0(float): Latitude of the upper left corner of the image + lon1(float): longitude of the next point clockwise + lat0(float): latitude of the next point clockwise + lon2(float): longitude of the next point clockwise + lat2(float): longitude of the next point clockwise + lon3(float): latitude of the next point clockwise + lat3(float): latitude of the next point clockwise + pixmap(QPixmap): Pixmap. + scene(MapGraphicsScene): Scene the item belongs to. + parent(QGraphicsItem): Parent item. + + Show a pixamp with geo-registered corners + """ + QGraphicsPixmapItem.__init__(self, parent=parent) + MapItem.__init__(self) + + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + self._lon2 = lon2 + self._lat2 = lat2 + self._lon3 = lon3 + self._lat3 = lat3 + self._xsize = 0 + self._ysize = 0 + self.setPixmap(pixmap) + self.setShapeMode(1) + self.x_mult = 1 + self.y_mult = 1 + + def updatePosition(self, scene): + + # 1. Get pix coords for each lat/lon point + pos0 = scene.posFromLonLat(self._lon0, self._lat0) + pos1 = scene.posFromLonLat(self._lon1, self._lat1) + pos2 = scene.posFromLonLat(self._lon2, self._lat2) + pos3 = scene.posFromLonLat(self._lon3, self._lat3) + self.prepareGeometryChange() + + # Set the image to 0, 0, then use a transform to + # to translate, rotate and warp it to the map + + # tranfsorm and scale + self.setPos(0, 0) + t = QTransform() + poly1 = QPolygonF() + + w = self.pixmap().width() + h = self.pixmap().height() + + poly1.append(QPointF( 0, 0 )) + poly1.append(QPointF( w, 0 )) + poly1.append(QPointF( w, h )) + poly1.append(QPointF( 0, h )) + + poly2 = QPolygonF() + poly2.append(QPointF(pos0[0], pos0[1])) + poly2.append(QPointF(pos1[0], pos1[1])) + poly2.append(QPointF(pos2[0], pos2[1])) + poly2.append(QPointF(pos3[0], pos3[1])) + success = QTransform.quadToQuad(poly1, poly2, t) + if not success: + logging.error('Unable to register image') + + self.setTransform(t) + + + def getGeoRect(self): + ''' get geo referenced rectangle for this object + + Returns: + QRectF (upper left x, upper left y, width, height) + ''' + pos0 = self.scene().posFromLonLat(self._lon0, self._lat0) + pos1 = self.scene().posFromLonLat(self._lon1, self._lat1) + xsize = abs(int(pos1[0] - pos0[0])) + ysize = abs(int(pos0[1] - pos1[1])) + ul_x = min(pos0[0], pos1[0]) + ul_y = min(pos0[1], pos1[1]) + rect = QRectF(ul_x, ul_y, xsize, ysize) + return rect + + + def setLonLat(self, lon0, lat0, lon1, lat1): + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + scene = self.scene() + if scene is not None: + self.updatePosition(self.scene()) + +# end MapGraphicsGeoPixmap + + + + +class MapGraphicsGeoPixmapItem(QGraphicsPixmapItem, MapItem): + + QtParentClass = QGraphicsPixmapItem + + def __init__(self, lon0, lat0, lon1, lat1, pixmap, parent=None): + """Constructor. + + Args: + longitude(float): Longitude of the upper left corner + latitude(float): Latitude of the upper left corner + longitude(float): Longitude of the lower right corner + latitude(float): Latitude of the lower right corner + pixmap(QPixmap): Pixmap. + scene(MapGraphicsScene): Scene the item belongs to. + parent(QGraphicsItem): Parent item. + + Show a pixamp with geo-registered corners + """ + QGraphicsPixmapItem.__init__(self, parent=parent) + MapItem.__init__(self) + + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + self._xsize = 0 + self._ysize = 0 + + self.orig_pixmap = pixmap + self.setPixmap(pixmap) + self.setShapeMode(1) + self.x_mult = 1 + self.y_mult = 1 + + def updatePosition(self, scene): + pos0 = scene.posFromLonLat(self._lon0, self._lat0) + pos1 = scene.posFromLonLat(self._lon1, self._lat1) + self.prepareGeometryChange() + xsize = abs(int(pos1[0] - pos0[0])) + ysize = abs(int(pos0[1] - pos1[1])) + + rect = scene.sceneRect() + x = rect.x() + y = rect.y() + width = rect.width() + height = rect.height() + self.ul_x = min(pos0[0], pos1[0]) + self.ul_y = min(pos0[1], pos1[1]) + self.lr_x = max(pos0[0], pos1[0]) + self.lr_y = max(pos0[1], pos1[1]) + + + #if xsize != self._xsize or ysize != self._ysize: + self._xsize = xsize + self._ysize = ysize + self.x_mult = xsize / self.orig_pixmap.width() + self.y_mult = ysize / self.orig_pixmap.width() + if 1: + newscale = QSize(xsize, ysize) + scaled = self.orig_pixmap.scaled(newscale) + self.setPixmap(scaled) + self.ul_x = min(pos0[0], pos1[0]) + self.ul_y = min(pos0[1], pos1[1]) + self.setPos(self.ul_x, self.ul_y) + + + def getGeoRect(self): + ''' get geo referenced rectangle for this object + + Returns: + QRectF (upper left x, upper left y, width, height) + ''' + pos0 = self.scene().posFromLonLat(self._lon0, self._lat0) + pos1 = self.scene().posFromLonLat(self._lon1, self._lat1) + xsize = abs(int(pos1[0] - pos0[0])) + ysize = abs(int(pos0[1] - pos1[1])) + ul_x = min(pos0[0], pos1[0]) + ul_y = min(pos0[1], pos1[1]) + rect = QRectF(ul_x, ul_y, xsize, ysize) + return rect + + + def setLonLat(self, lon0, lat0, lon1, lat1): + self._lon0 = lon0 + self._lat0 = lat0 + self._lon1 = lon1 + self._lat1 = lat1 + scene = self.scene() + if scene is not None: + self.updatePosition(self.scene()) + +# end MapGraphicsGeoPixmap + class MapGraphicsPixmapItem(QGraphicsPixmapItem, MapItem): """Item for showing a pixmap in a MapGraphicsScene. @@ -268,7 +681,7 @@ def __init__(self, longitude, latitude, pixmap, parent=None): """Constructor. Args: - longitude(float): Longitude of the origin of the pixmap. + longitude(float): Longitude of the center of the pixmap latitude(float): Latitude of the center of the pixmap. pixmap(QPixmap): Pixmap. scene(MapGraphicsScene): Scene the item belongs to. @@ -281,6 +694,20 @@ def __init__(self, longitude, latitude, pixmap, parent=None): self._lat = latitude self.setPixmap(pixmap) + def getGeoRect(self): + ''' get geo referenced rectangle for this object + + Returns: + QRectF (upper left x, upper left y, width, height) + ''' + rect = self.boundingRect() + pos = self.scene().posFromLonLat(self._lon, self._lat) + w = rect.width() + h = rect.height() + rect2 = QRectF(pos[0], pos[1], w, h ) + return rect2 + + def updatePosition(self, scene): """Update the origin position of the item. @@ -291,7 +718,12 @@ def updatePosition(self, scene): """ pos = scene.posFromLonLat(self._lon, self._lat) self.prepareGeometryChange() - self.setPos(pos[0], pos[1]) + rect = self.boundingRect() + w = rect.width() + h = rect.height() + self.setPos(pos[0] - h//2, pos[1] -w//2) + if self._label_item: + self._label_item.updatePosition(scene) def setLonLat(self, longitude, latitude): """Update the origin coordinates of the item. @@ -308,8 +740,7 @@ def setLonLat(self, longitude, latitude): if scene is not None: self.updatePosition(scene) - -class MapGraphicsTextItem(QGraphicsSimpleTextItem, MapItem): +class MapGraphicsTextItem(QGraphicsTextItem, MapItem): """Text item for the MapGraphicsScene """ @@ -320,6 +751,9 @@ def __init__(self, longitude, latitude, text, parent=None, min_zoom_visibility=N MapItem.__init__(self) self._min_zoom = min_zoom_visibility self._lon, self._lat = longitude, latitude + self._border = QGraphicsRectItem(parent=self) + self._border.setPen(QPen(Qt.NoPen)) + self._border.setBrush(QBrush(QColor(190, 190, 190, 160))) def resetMinZoomVisibility(self): """Delete level of zoom under which the text disappears. """ @@ -333,7 +767,39 @@ def updatePosition(self, scene): """Update the origin position of the item.""" pos = scene.posFromLonLat(self._lon, self._lat) - self.setPos(pos) + self.setPos(pos[0], pos[1]) + if self._min_zoom is not None: + self.setVisible(scene._zoom >= self._min_zoom) + #rect = super(MapGraphicsTextItem, self).boundingRect() + #self._border.setRect() + +class MapGraphicsLabelItem(QGraphicsTextItem, MapItem): + """ Label for an item - updates its position with the item + """ + + QtParentClass = QGraphicsSimpleTextItem + + def __init__(self, other_item, text, parent=None, min_zoom_visibility=None): + QGraphicsSimpleTextItem.__init__(self, text, parent=parent) + MapItem.__init__(self) + self.other_item = other_item + self._min_zoom = min_zoom_visibility + self._border = QGraphicsRectItem(parent=self) + self._border.setPen(QPen(Qt.NoPen)) + self._border.setBrush(QBrush(QColor(190, 190, 190, 160))) + + def resetMinZoomVisibility(self): + """Delete level of zoom under which the text disappears. """ + self._min_zoom = None + + def setMinZoomVisibility(self, zoom_level): + """Update level of zoom under which the text disappears. """ + self._min_zoom = zoom_level + + def updatePosition(self, scene): + """Update the origin position of the item.""" + pos = self.other_item.getLabelLocation() + self.setPos(pos[0], pos[1]) if self._min_zoom is not None: self.setVisible(scene._zoom >= self._min_zoom) @@ -380,7 +846,7 @@ def updatePosition(self, scene): x, y = scene.posFromLonLat(self._longitudes, self._latitudes) lines = self._lines - for i in iterRange(0, len(lines)-1): + for i in iterRange(0, len(lines)): lines[i].setLine(x[i], y[i], x[i+1], y[i+1]) def setLonLat(self, longitudes, latitudes): @@ -407,3 +873,4 @@ def setLonLat(self, longitudes, latitudes): def __getitem__(self, index): return self._lines[index] + diff --git a/pytilemap/mapnavitem.py b/pytilemap/mapnavitem.py new file mode 100644 index 0000000..1432e9f --- /dev/null +++ b/pytilemap/mapnavitem.py @@ -0,0 +1,150 @@ +from __future__ import print_function, absolute_import + +import os +from qtpy.QtCore import Qt, Slot, QRectF, QPointF, QObject, Signal +from qtpy.QtGui import QPen, QBrush, QColor, QPixmap +from qtpy.QtWidgets import QGraphicsObject, QGraphicsRectItem, QGraphicsItemGroup, \ + QGraphicsSimpleTextItem, QGraphicsEllipseItem, QGraphicsLineItem, QGraphicsPixmapItem + +from .imagebutton import ImageButton +from .mapitems import MapItem +from .functions import makePen, makeBrush +from .qtsupport import getQVariantValue + +from .maplegenditem import * + + +class MapNavItem(QGraphicsObject, MapItem): + + QtParentClass = QGraphicsObject + + _posForAnchors = { + Qt.TopLeftCorner: QPointF(20.0, 75.0), + Qt.TopRightCorner: QPointF(40.0, -15.0), + Qt.BottomLeftCorner: QPointF(20.0, -15.0), + Qt.BottomRightCorner: QPointF(30.0, 75.0), + } + + + def __init__(self, anchor, parent=None): + QGraphicsObject.__init__(self, parent=parent) + MapItem.__init__(self) + self.setZValue(200.0) + + anchorPos = self._posForAnchors[anchor] + self._anchorPos = QPointF(anchorPos) + self._anchor = anchor + + self._border = QGraphicsRectItem(parent=self) + self._border.setPen(QPen(Qt.NoPen)) + self._border.setBrush(QBrush(QColor(190, 190, 190, 160))) + + self._entries = list() + + imgfile = os.path.dirname(__file__) + os.sep + 'zoom_in_symbol.png' + img = QPixmap(24,24) + img.load(imgfile) + img = img.scaled(24,24) + img = ImageButton(img, parent=self) + self.zoom_in_button = img + self.addEntry(self.zoom_in_button) + + imgfile = os.path.dirname(__file__) + os.sep + 'zoom_out_symbol.png' + img2 = QPixmap(24,24) + img2.load(imgfile) + img2 = img2.scaled(24,24) + img2 = ImageButton(img2, parent=self) + self.zoom_out_button = img2 + self.addEntry(self.zoom_out_button) + + def _sceneChanged(self, oldScene, newScene): + if oldScene is not None: + oldScene.sceneRectChanged.disconnect(self.setSceneRect) + if newScene is not None: + newScene.sceneRectChanged.connect(self.setSceneRect) + # Setup the new position of the item + self.setSceneRect(newScene.sceneRect()) + + def updatePosition(self, scene): + pass + + def addRect(self, text, color, border=None, size=20.0): + shape = QGraphicsRectItem(size / 2.0, size / 2.0, size, size) + brush = makeBrush(color) + shape.setBrush(brush) + shape.setPen(makePen(border)) + + self.addEntry(MapLegendEntryItem(shape, text)) + + def addEntry(self, entry): + self._entries.append(entry) + self._updateLayout() + + def boundingRect(self): + return self._border.boundingRect() + + def paint(*args, **kwargs): + pass + + @Slot(QRectF) + def setSceneRect(self, rect): + anchorPos = self._anchorPos + anchor = self._anchor + newPos = None + if anchor == Qt.BottomRightCorner: + newPos = rect.bottomRight() - anchorPos + elif anchor == Qt.TopRightCorner: + newPos = rect.topRight() - anchorPos + elif anchor == Qt.TopLeftCorner: + newPos = rect.topLeft() + anchorPos + elif anchor == Qt.BottomLeftCorner: + newPos = rect.bottomLeft() + anchorPos + else: + raise NotImplementedError('Other corner have not actually been implemented') + + self.setPos(newPos) + + + def _updateLayout(self): + self.prepareGeometryChange() + + bottom = 0.0 + left = 0.0 + right = 0.0 + for entry in self._entries: + entry.setPos(left, bottom) + bottom += entry.boundingRect().bottom() + right = max(right, entry.boundingRect().right() + 1.0) + + self._border.setRect(0.0, 0.0, right, bottom + 1.0) + + def pen(self): + """Pen for the background of the legend + + Returns: + QPen: Pen for the background of the legend + """ + return self._border.pen() + + def brush(self): + """Brush for the background of the legend + + Returns: + QBrush: Brush for the background of the legend + """ + return self._border.brush() + + def setPen(self, *args, **kwargs): + """Set the pen for the background of the legend + + The arguments are the same of the :func:`makePen` function + """ + return self._border.setPen(makePen(*args, **kwargs)) + + def setBrush(self, *args, **kwargs): + """Set the brush for the background of the legend + + The arguments are the same of the :func:`makeBrush` function + """ + return self._border.setBrush(makeBrush(*args, **kwargs)) + diff --git a/pytilemap/mapscene.py b/pytilemap/mapscene.py index 604ea1e..accb283 100644 --- a/pytilemap/mapscene.py +++ b/pytilemap/mapscene.py @@ -1,16 +1,22 @@ from __future__ import print_function, absolute_import, division from numpy import floor +import math +import os -from qtpy.QtCore import Qt, Slot, Signal, QRect, QRectF, QPointF, QSizeF -from qtpy.QtGui import QPixmap -from qtpy.QtWidgets import QGraphicsScene +from qtpy.QtCore import Qt, Slot, Signal, QRect, QRectF, QPointF, QSizeF, QPoint, QSize +from qtpy.QtGui import QPixmap, QPen, QBrush, QColor, QPainter +from qtpy.QtWidgets import QGraphicsScene, QGraphicsLineItem, QGraphicsRectItem, QGraphicsItem + +from qtpy.QtSvg import QGraphicsSvgItem from .mapitems import MapGraphicsCircleItem, MapGraphicsLineItem, \ MapGraphicsPolylineItem, MapGraphicsPixmapItem, MapGraphicsTextItem, \ - MapGraphicsRectItem, MapGraphicsLinesGroupItem + MapGraphicsRectItem, MapGraphicsLinesGroupItem, MapGraphicsGeoPixmapItem, \ + MapGraphicsLabelItem, MapGraphicsGeoSvgItem, MapGraphicsRectShapeItem, MapGraphicsGeoPixmapItemCorners from .maplegenditem import MapLegendItem from .mapescaleitem import MapScaleItem +from .mapnavitem import MapNavItem from .functions import iterRange from .tileutils import posFromLonLat, lonLatFromPos @@ -20,6 +26,8 @@ class MapGraphicsScene(QGraphicsScene): """ sigZoomChanged = Signal(int) + sigSelectionDrawn = Signal(float, float, float, float) + customSceneRectChanged = Signal(float,float,float,float) def __init__(self, tileSource, parent=None): """Constructor. @@ -30,7 +38,7 @@ def __init__(self, tileSource, parent=None): """ QGraphicsScene.__init__(self, parent=parent) - self._zoom = 15 + self._zoom = 8 self._tileSource = tileSource self._tileSource.setParent(self) @@ -48,6 +56,12 @@ def __init__(self, tileSource, parent=None): self.setSceneRect(0.0, 0.0, 400, 300) self.sceneRectChanged.connect(self.onSceneRectChanged) + # Rubberband Support for Drawing Areas + self.rect_start = None + self.rect_end = None + self.rubberband = None + self.rubberband_enabled = False + @Slot() def close(self): self._tileSource.close() @@ -68,6 +82,63 @@ def setTileSource(self, newTileSource): self.invalidate() self.update() + def mousePressEvent(self, evt): + '''Catch right-click events for rectangle drawing''' + if self.rubberband_enabled and evt.button() == 2: + evt.accept() + pos = evt.scenePos() + self.rect_start = pos + + if self.rubberband != None: + self.removeItem(self.rubberband) + self.rubberband = None + + else: + evt.ignore() + QGraphicsScene.mousePressEvent(self, evt) + + def mouseReleaseEvent(self, evt): + '''Catch right-click events for rectangle drawing''' + if self.rubberband_enabled and evt.button() == 2: + evt.accept() + pos = evt.scenePos() + lon0, lat0 = self.lonLatFromPos(self.rect_start.x(), self.rect_start.y()) + lon1,lat1 = self.lonLatFromPos(pos.x(), pos.y()) + self.removeItem(self.rubberband) + + self.rect_start = None + self.rect_end = None + self.rubberband = None + + self.sigSelectionDrawn.emit(lon0, lat0, lon1, lat1) + + else: + evt.ignore() + QGraphicsScene.mouseReleaseEvent(self, evt) + + def mouseMoveEvent(self, evt): + '''Catch right-click events for rectangle drawing''' + if self.rubberband_enabled and self.rect_start: + pos = evt.scenePos() + #lon,lat = self.lonLatFromPos(pos.x(), pos.y()) + self.rect_end = pos + if not self.rubberband: + self.rubberband = QGraphicsRectItem( + min(self.rect_start.x(), self.rect_end.x()), + min(self.rect_start.y(), self.rect_end.y()), + abs(self.rect_end.x()-self.rect_start.x()), + abs(self.rect_end.y()-self.rect_start.y())) + clr = QColor(240,240,240,100) + self.rubberband.setBrush(clr) + self.rubberband.setPen(QPen(QBrush(Qt.blue), 1.0)) + self.addItem(self.rubberband) + else: + self.rubberband.setRect( + min(self.rect_start.x(), self.rect_end.x()), + min(self.rect_start.y(), self.rect_end.y()), + abs(self.rect_end.x()-self.rect_start.x()), + abs(self.rect_end.y()-self.rect_start.y())) + @Slot(QRectF) def onSceneRectChanged(self, rect): """Callback for the changing of the visible rect. @@ -105,6 +176,10 @@ def onSceneRectChanged(self, rect): self.invalidate() self.update() + lon0, lat0 = self.lonLatFromPos(rect.x(), rect.y()) + lon1, lat1 = self.lonLatFromPos(rect.x() + rect.width(), rect.y() + rect.height()) + + self.customSceneRectChanged.emit(lon0, lat0, lon1, lat1) def drawBackground(self, painter, rect): """Draw the background tiles. @@ -176,7 +251,7 @@ def zoomIn(self, pos=None): current center position. """ if pos is None: - pos = self.sceneRect().center() + pos = QPoint(self.width()/2, self.height()/2) self.zoomTo(pos, self._zoom + 1) def zoomOut(self, pos=None): @@ -187,9 +262,17 @@ def zoomOut(self, pos=None): current center position. """ if pos is None: - pos = self.sceneRect().center() + pos = QPoint(self.width()/2, self.height()/2) self.zoomTo(pos, self._zoom - 1) + @Slot() + def handleZoomIn(self): + self.zoomIn() + + @Slot() + def handleZoomOut(self): + self.zoomOut() + def zoom(self): return self._zoom @@ -224,8 +307,8 @@ def requestTiles(self): zoom = self._zoom # Request load of new tiles - for x in iterRange(numXtiles): - for y in iterRange(numYtiles): + for x in iterRange(numXtiles+1): + for y in iterRange(numYtiles+1): tp = (left + x, top + y) # Request tile only if missing if tp not in tilePixmaps: @@ -260,7 +343,7 @@ def setSize(self, width, height): rect = QRectF(self.sceneRect().topLeft(), QSizeF(width, height)) self.setSceneRect(rect) - def setCenter(self, lon, lat): + def setCenter(self, lon, lat, zoom=None): """Move the center of the visible area to new coordinates. Update the scene rect. @@ -268,12 +351,17 @@ def setCenter(self, lon, lat): Args: lon(float): New longitude of the center. lat(float): New latitude of the center. + zoom(int [1:15]): Zoom Level """ + if zoom != None and zoom < 15 and zoom > 0: + self._zoom = zoom + rect = QRectF(self.sceneRect()) pos = self.posFromLonLat(lon, lat) rect.moveCenter(QPointF(pos[0], pos[1])) self.setSceneRect(rect) + def center(self): centerPos = self.sceneRect().center() centerCoord = self.lonLatFromPos(centerPos.x(), centerPos.y()) @@ -331,6 +419,23 @@ def tileFromPos(self, x, y): tdim = float(self._tileSource.tileSize()) return QPointF(x / tdim, y / tdim) + def addRectShape(self, longitude, latitude, width, height): + """Add a new rectangle with fixed width/height + + Args: + longitude(float): Longitude of the top left. + latitude(float): Latitude of the top left + width (float): width in pixels + height(float): height in pixels + + Returns: + MapGraphicsCircleItem added to the scene. + """ + + item = MapGraphicsRectShapeItem(longitude, latitude, width, height) + self.addItem(item) + return item + def addCircle(self, longitude, latitude, radius): """Add a new circle to the graphics scene. @@ -392,6 +497,23 @@ def addPolyline(self, longitudes, latitudes): self.addItem(item) return item + def addPin(self, lon, lat): + """Add a location pin to the graphics scene. + + Args: + longitude(float): Longitude (decimal degrees WGS84) of the pin + latitude(float): Latitude of the Pin + + Returns: + MapGraphicsPixmapItem added to the scene. + """ + pinfile = os.path.dirname(__file__) + os.sep + 'red_pin.png' + pixmap = QPixmap() + pixmap.load(pinfile) + item = MapGraphicsPixmapItem(lon, lat, pixmap) + self.addItem(item) + return item + def addPixmap(self, longitude, latitude, pixmap): """Add a new circle (point) to the graphics scene. @@ -411,6 +533,61 @@ def addPixmap(self, longitude, latitude, pixmap): self.addItem(item) return item + def addGeoSvg(self, lon0, lat0, lon1, lat1, svg): + '''Add a geo-registered pixmap to the scene + + Args: + lon0(float): Longitude (decimal degress WGS84) upper left + lat0(float): Lattitude (decimal degrees WGS84) upper left + lon1(float): Longitude lower right + lat1(float): Lattitudelower right + + Returns: + MapGraphicsGeoPixmapItem + ''' + item = MapGraphicsGeoSvgItem(lon0, lat0, lon1, lat1, svg) + self.addItem(item) + return item + + + def addGeoPixmap(self, lon0, lat0, lon1, lat1, pixmap): + '''Add a geo-registered pixmap to the scene + + Args: + lon0(float): Longitude (decimal degress WGS84) upper left + lat0(float): Lattitude (decimal degrees WGS84) upper left + lon1(float): Longitude lower right + lat1(float): Lattitudelower right + + Returns: + MapGraphicsGeoPixmapItem + ''' + item = MapGraphicsGeoPixmapItem(lon0, lat0, lon1, lat1, pixmap) + self.addItem(item) + return item + + def addGeoPixmapCorners(self, lon0, lat0, lon1, lat1, lon2, lat2, lon3, lat3, pixmap): + '''Add a geo-registered pixmap to the scene using 4 lat-lon corners + + Args: + lon0(float): Longitude (decimal degress WGS84) upper left of image + lat0(float): Lattitude (decimal degrees WGS84) upper left of image + lon1(float): Lat of next point clockwise + lat1(float): Lon of next point clockwise + lon2(float): Lat of next point clockwise + lat2(float): Lon of next point clockwise + lon3(float): Lat of next point clockwise + lat3(float): Lon of next point clockwise + + Returns: + MapGraphicsGeoPixmapItem + ''' + item = MapGraphicsGeoPixmapItemCorners(lon0, lat0, lon1, lat1, + lon2, lat2, lon3, lat3, pixmap) + self.addItem(item) + return item + + def addText(self, longitude, latitude, text): """Add a test item to the graphics scene. @@ -425,6 +602,13 @@ def addText(self, longitude, latitude, text): self.addItem(item) return item + def addNavItem(self, anchor): + self.nav_item = MapNavItem(anchor) + self.addItem(self.nav_item) + self.nav_item.zoom_in_button.clicked.connect(self.handleZoomIn) + self.nav_item.zoom_out_button.clicked.connect(self.handleZoomOut) + return self.nav_item + def addLegend(self, pos=QPointF(10.0, 10.0)): legend = MapLegendItem(pos=pos) self.addItem(legend) @@ -450,6 +634,7 @@ def addScale(self, **kwargs): self.addItem(scaleItem) return scaleItem + def addLinesGroup(self, longitudes, latitudes): item = MapGraphicsLinesGroupItem(longitudes, latitudes) self.addItem(item) diff --git a/pytilemap/maptilesources/maptilesource.py b/pytilemap/maptilesources/maptilesource.py index 92b3db9..87d6962 100644 --- a/pytilemap/maptilesources/maptilesource.py +++ b/pytilemap/maptilesources/maptilesource.py @@ -12,7 +12,7 @@ class MapTileSource(QObject): _minZoom = None _maxZoom = None - def __init__(self, tileSize=256, minZoom=2, maxZoom=18, parent=None): + def __init__(self, tileSize=256, minZoom=2, maxZoom=17, parent=None): QObject.__init__(self, parent=parent) self._tileSize = tileSize self._minZoom = minZoom diff --git a/pytilemap/maptilesources/maptilesourcehere.py b/pytilemap/maptilesources/maptilesourcehere.py index 62c5ffa..3e19d35 100644 --- a/pytilemap/maptilesources/maptilesourcehere.py +++ b/pytilemap/maptilesources/maptilesourcehere.py @@ -10,28 +10,34 @@ class MapTileSourceHereDemo(MapTileSourceHTTP): def __init__(self, tileSize=256, parent=None): MapTileSourceHTTP.__init__(self, tileSize=tileSize, minZoom=2, maxZoom=20, parent=parent) assert tileSize == 256 or tileSize == 512 - self._server = 0 + self._server = 1 + self._cache_dir = 'api.here.com' def url(self, x, y, zoom): self._server += 1 if self._server > 4: - self._server = 0 - url = "http://%d.base.maps.cit.api.here.com/maptile/2.1/maptile/" % self._server + self._server = 1 + #https://2.aerial.maps.cit.api.here.com/maptile/2.1/maptile/newest/hybrid.day/5/8/13/256/png8 + url = "http://%d.aerial.maps.cit.api.here.com/maptile/2.1/maptile/" % self._server + #url = "http://%d.base.maps.cit.api.here.com/maptile/2.1/maptile/" % self._server url += "newest/normal.day/%d/%d/%d/%d/png8" % (zoom, x, y, self._tileSize) url += '?app_id=DemoAppId01082013GAL&app_code=AJKnXv84fjrb0KIHawS0Tg' + print (url) return url class MapTileSourceHere(MapTileSourceHTTP): def __init__(self, tileSize=256, app_id='DemoAppId01082013GAL', app_code='AJKnXv84fjrb0KIHawS0Tg', + #scheme='hybrid.day', cit=True, tileType='maptile', mapType='aerial', imageFmt='png8', scheme='normal.day', cit=True, tileType='maptile', mapType='base', imageFmt='png8', userAgent='(PyQt) TileMap 1.0 - HERE', mapHttpLoader=None, minZoom=2, maxZoom=20, parent=None): MapTileSourceHTTP.__init__(self, tileSize=tileSize, minZoom=minZoom, maxZoom=maxZoom, mapHttpLoader=mapHttpLoader, parent=parent) + self._cache_dir = 'api.here.com' assert tileSize == 256 or tileSize == 512 - self._server = 0 + self._server = 1 self._app_id = app_id self._app_code = app_code @@ -64,7 +70,7 @@ def setOptions(self, scheme=None, tileType=None, mapType=None): def url(self, x, y, zoom): self._server += 1 if self._server > 4: - self._server = 0 + self._server = 1 args = (self._server, zoom, x, y) url = self._baseurl % args diff --git a/pytilemap/maptilesources/maptilesourcehttp.py b/pytilemap/maptilesources/maptilesourcehttp.py index 4cb47ca..d4f8301 100644 --- a/pytilemap/maptilesources/maptilesourcehttp.py +++ b/pytilemap/maptilesources/maptilesourcehttp.py @@ -8,7 +8,10 @@ from .maptilesource import MapTileSource from ..qtsupport import getQVariantValue, getCacheFolder -DEFAULT_CACHE_SIZE = 1024 * 1024 * 100 +import os +from urllib.parse import urlparse + +DEFAULT_CACHE_SIZE = 1024 * 1024 * 16000 class MapTileHTTPLoader(QObject): @@ -31,35 +34,46 @@ def __init__(self, cacheSize=DEFAULT_CACHE_SIZE, userAgent='(PyQt) TileMap 1.0', self._userAgent = userAgent self._tileInDownload = dict() - @Slot(int, int, int, str) - def loadTile(self, x, y, zoom, url): + @Slot(int, int, int, str, str) + def loadTile(self, x, y, zoom, url, cache_dir): if self._manager is None: self._manager = QNetworkAccessManager(parent=self) self._manager.finished.connect(self.handleNetworkData) - cache = QNetworkDiskCache() - cacheDir = getCacheFolder() - cache.setCacheDirectory(cacheDir) - cache.setMaximumCacheSize(self._cacheSize) - self._manager.setCache(cache) + self._cache = './tiles' key = (x, y, zoom) url = QUrl(url) + #base = parsed_url.netloc if key not in self._tileInDownload: - # Request the image to the map service - request = QNetworkRequest(url=url) - request.setRawHeader(b'User-Agent', self._userAgent) - request.setAttribute(QNetworkRequest.User, key) - request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferCache) - self._tileInDownload[key] = self._manager.get(request) + path = os.path.join(self._cache, str(cache_dir), str(zoom), str(x), str(y)+'.png') + if os.path.exists(path): + self.tileLoaded.emit(x, y, zoom, open(path, 'rb').read()) + else: + # Request the image to the map service + request = QNetworkRequest(url=url) + request.setRawHeader(b'User-Agent', self._userAgent) + request.setAttribute(QNetworkRequest.User, [key, cache_dir]) + request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferCache) + self._tileInDownload[key] = self._manager.get(request) + @Slot(QNetworkReply) def handleNetworkData(self, reply): - tp = getQVariantValue(reply.request().attribute(QNetworkRequest.User)) + [tp, cache_dir] = getQVariantValue(reply.request().attribute(QNetworkRequest.User)) if tp in self._tileInDownload: del self._tileInDownload[tp] + zoom = tp[2]; x = tp[0]; y = tp[1] + + base = cache_dir + if not reply.error(): data = reply.readAll() + if not os.path.isdir(os.path.join(self._cache, base, str(zoom), str(x))): + os.makedirs(os.path.join(self._cache, base, str(zoom), str(x))) + fout = open(os.path.join(self._cache, base, str(zoom), str(x), str(y)+'.png'), 'wb') + fout.write(data) + fout.close() self.tileLoaded.emit(tp[0], tp[1], tp[2], data) reply.close() reply.deleteLater() @@ -91,6 +105,7 @@ def __init__(self, cacheSize=DEFAULT_CACHE_SIZE, userAgent='(PyQt) TileMap 1.0', self._loader = MapTileHTTPLoader(cacheSize=cacheSize, userAgent=userAgent) self._loader.tileLoaded.connect(self.handleTileDataLoaded) + self._cache_dir = 'cache' @Slot() def close(self): @@ -101,7 +116,7 @@ def url(self, x, y, zoom): def requestTile(self, x, y, zoom): url = self.url(x, y, zoom) - self._loader.loadTile(x, y, zoom, url) + self._loader.loadTile(x, y, zoom, url, self._cache_dir) @Slot(int, int, int, QByteArray) def handleTileDataLoaded(self, x, y, zoom, data): diff --git a/pytilemap/maptilesources/maptilesourceosm.py b/pytilemap/maptilesources/maptilesourceosm.py index 72d6c4a..bdf9efa 100644 --- a/pytilemap/maptilesources/maptilesourceosm.py +++ b/pytilemap/maptilesources/maptilesourceosm.py @@ -7,6 +7,7 @@ class MapTileSourceOSM(MapTileSourceHTTP): def __init__(self, parent=None): MapTileSourceHTTP.__init__(self, parent=parent) + self._cache_dir = 'tile.openstreetmap.org' def url(self, x, y, zoom): url = "http://tile.openstreetmap.org/%d/%d/%d.png" % (zoom, x, y) diff --git a/pytilemap/red_pin.png b/pytilemap/red_pin.png new file mode 100644 index 0000000..e4ef1c1 Binary files /dev/null and b/pytilemap/red_pin.png differ diff --git a/pytilemap/zoom_in_symbol.png b/pytilemap/zoom_in_symbol.png new file mode 100644 index 0000000..6787135 Binary files /dev/null and b/pytilemap/zoom_in_symbol.png differ diff --git a/pytilemap/zoom_out_symbol.png b/pytilemap/zoom_out_symbol.png new file mode 100644 index 0000000..c49ba6a Binary files /dev/null and b/pytilemap/zoom_out_symbol.png differ diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..b45c566 Binary files /dev/null and b/screenshot.png differ diff --git a/setup.py b/setup.py index 2f236e7..5f1bc61 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,9 @@ description='Widget for tile maps in PyQt', long_description=long_description, + # Required for image files + include_package_data=True, + # The project's main homepage. url='https://github.com/allebacco/PyTileMap',