def _apply_named_style(layer, geo_type):
     """
      * Looks for a styles with the same name as the layer and if one is found, it is applied to the layer
     :param layer: 
     :param layer_path: e.g. 'transportation.service' or 'transportation_name.path'
     :return: 
     """
     try:
         name = layer.name().split(VtReader._zoom_level_delimiter)[0].lower()
         styles = [
             "{}.{}".format(name, geo_type.lower()),
             name
         ]
         for p in styles:
             style_name = "{}.qml".format(p).lower()
             if style_name in VtReader._styles:
                 style_path = os.path.join(FileHelper.get_plugin_directory(), "styles/{}".format(style_name))
                 res = layer.loadNamedStyle(style_path)
                 if res[1]:  # Style loaded
                     layer.setCustomProperty("layerStyle", style_path)
                     if layer.customProperty("layerStyle") == style_path:
                         debug("Style successfully applied: {}", style_name)
                         break
     except:
         critical("Loading style failed: {}", sys.exc_info())
 def _connect_to_db(self):
     """
      * Since an mbtile file is a sqlite database, we can connect to it
     """
     debug("Connecting to: {}", self.path)
     try:
         self.conn = sqlite3.connect(self.path)
         self.conn.row_factory = sqlite3.Row
         debug("Successfully connected")
     except:
         critical("Db connection failed:", sys.exc_info())
    def cache_tile(tile, file_name):
        if not tile.decoded_data:
            warn("Trying to cache a tile without data: {}", tile)

        file_path = os.path.join(FileHelper.get_cache_directory(), file_name)
        try:
            with open(file_path, 'wb') as f:
                pickle.dump(tile, f, pickle.HIGHEST_PROTOCOL)
        except:
            critical("Error while writing tile '{}' to cache: {}", str(tile),
                     sys.exc_info()[1])
 def _get_from_db(self, sql):
     if not self.conn:
         debug("Not connected yet.")
         self._connect_to_db()
     try:
         debug("Execute SQL: {}", sql)
         cur = self.conn.cursor()
         cur.execute(sql)
         return cur.fetchall()
     except:
         critical("Getting data from db failed: {}", sys.exc_info())
예제 #5
0
 def _create_reader(self, path_or_url):
     # A lazy import is required because the vtreader depends on the external libs
     from vt_reader import VtReader
     reader = None
     try:
         reader = VtReader(self.iface,
                           path_or_url=path_or_url,
                           progress_handler=self.handle_progress_update)
     except RuntimeError:
         QMessageBox.critical(None, "Loading Error", str(sys.exc_info()[1]))
         critical(str(sys.exc_info()[1]))
     return reader
    def export(self):
        self._cancel_requested = False
        self.conn = None
        try:
            self.conn = self._create_db()
        except:
            critical("db creation failed: {}", sys.exc_info())

        if self.conn:
            try:
                with self.conn:
                    tile_names = self._get_loaded_tile_names()
                    if tile_names:
                        tiles = self._load_tiles(tile_names)
                        nr_tiles = len(tiles)
                        for index, t in enumerate(tiles):
                            if self._cancel_requested:
                                break

                            self._update_progress(
                                title="Export tile {}/{}".format(
                                    index + 1, nr_tiles),
                                show_dialog=True)
                            QApplication.processEvents()
                            self._update_bounds(t)
                            debug("layers to export: {}",
                                  self.layers_to_export)
                            self._save_tile(t)

                        if not self._cancel_requested:
                            layer_objects = map(lambda l: {"id": l},
                                                self.layer_names)
                            vector_layers = {"vector_layers": layer_objects}
                            self.metadata["json"] = json.dumps(vector_layers)
                            self._save_metadata()
                            debug("export complete")
                            self.iface.messageBar().pushInfo(
                                u'Vector Tiles Reader',
                                u'mbtiles export completed')
                        else:
                            debug("export cancelled")
                            self.iface.messageBar().pushInfo(
                                u'Vector Tiles Reader',
                                u'mbtiles export cancelled')
                self.conn.close()
            except:
                if self.conn:
                    self.conn.close()
                critical("Export failed: {}", sys.exc_info())
                raise
        self._update_progress(show_dialog=False)
예제 #7
0
 def load(self):
     debug("Loading TileJSON")
     success = False
     try:
         status, data = FileHelper.load_url(self.url)
         self.json = json.loads(data)
         if self.json:
             debug("TileJSON loaded")
             self._validate()
             success = True
         else:
             debug("Loading TileJSON failed")
             self.json = {}
             raise RuntimeError("TileJSON could not be loaded.")
     except:
         critical("Loading TileJSON failed ({}): {}", self.url, sys.exc_info())
     return success
 def _get_single_value(self, sql_query, field_name):
     """
      * Helper function that can be used to safely load a single value from the db
      * Returns the value or None if result is empty or execution of query failed
     :param sql_query: 
     :param field_name: 
     :return: 
     """
     value = None
     try:
         rows = self._get_from_db(sql=sql_query)
         if rows:
             value = rows[0][field_name]
             debug("Value is: {}".format(value))
     except:
         critical("Loading metadata value '{}' failed: {}", field_name, sys.exc_info())
     return value
예제 #9
0
 def _export_tiles(self):
     from vt_writer import VtWriter
     file_name = QFileDialog.getSaveFileName(
         None, "Export Vector Tiles", FileHelper.get_home_directory(),
         "mbtiles (*.mbtiles)")
     if file_name:
         self.export_action.setDisabled(True)
         try:
             self._current_writer = VtWriter(
                 self.iface,
                 file_name,
                 progress_handler=self.handle_progress_update)
             self._create_progress_dialog(self.iface.mainWindow(),
                                          on_cancel=self._cancel_export)
             self._current_writer.export()
         except:
             critical("Error during export: {}", sys.exc_info())
         self.export_action.setEnabled(True)
예제 #10
0
    def _load_tiles(self,
                    path,
                    options,
                    layers_to_load,
                    bounds=None,
                    ignore_limit=False):
        merge_tiles = options.merge_tiles_enabled()
        apply_styles = options.apply_styles_enabled()
        tile_limit = options.tile_number_limit()
        load_mask_layer = options.load_mask_layer_enabled()
        if ignore_limit:
            tile_limit = None
        manual_zoom = options.manual_zoom()
        cartographic_ordering = options.cartographic_ordering()

        if apply_styles:
            self._set_background_color()

        debug("Load: {}", path)
        reader = self._current_reader
        if reader:
            reader.enable_cartographic_ordering(enabled=cartographic_ordering)
            try:
                zoom = reader.source.max_zoom()
                if manual_zoom is not None:
                    zoom = manual_zoom
                reader.load_tiles(zoom_level=zoom,
                                  layer_filter=layers_to_load,
                                  load_mask_layer=load_mask_layer,
                                  merge_tiles=merge_tiles,
                                  apply_styles=apply_styles,
                                  max_tiles=tile_limit,
                                  bounds=bounds,
                                  limit_reacher_handler=lambda: self.
                                  _show_limit_exceeded_message(tile_limit))
                self.refresh_layers()
                debug("Loading complete!")
            except RuntimeError:
                QMessageBox.critical(None, "Unexpected exception",
                                     str(sys.exc_info()[1]))
                critical(str(sys.exc_info()[1]))
 def _create_reader(self, path_or_url):
     # A lazy import is required because the vtreader depends on the external libs
     from vt_reader import VtReader
     reader = None
     try:
         reader = VtReader(self.iface, path_or_url=path_or_url)
         reader.progress_changed.connect(self.reader_progress_changed)
         reader.max_progress_changed.connect(
             self.reader_max_progress_changed)
         reader.show_progress_changed.connect(
             self.reader_show_progress_changed)
         reader.title_changed.connect(self.reader_title_changed)
         reader.message_changed.connect(self.reader_message_changed)
         reader.loading_finished.connect(self.reader_loading_finished)
         reader.tile_limit_reached.connect(
             self.reader_limit_exceeded_message)
         reader.cancelled.connect(self.reader_cancelled)
     except RuntimeError:
         QMessageBox.critical(None, "Loading Error", str(sys.exc_info()[1]))
         critical(str(sys.exc_info()[1]))
     return reader
 def load(self):
     debug("Loading TileJSON")
     success = False
     try:
         if os.path.isfile(self.url):
             with open(self.url, 'r') as f:
                 data = f.read()
         else:
             status, data = FileHelper.load_url(self.url)
         self.json = json.loads(data)
         if self.json:
             debug("TileJSON loaded")
             self._validate()
             debug("TileJSON validated")
             success = True
         else:
             info("Parsing TileJSON failed")
             self.json = {}
             raise RuntimeError("TileJSON could not be loaded.")
     except:
         critical("Loading TileJSON failed ({}): {}", self.url, sys.exc_info())
     return success
    def _load_tiles(self):
        try:
            # recreate source to assure the source belongs to the new thread, SQLite3 isn't happy about it otherwise
            self.source = self._create_source(self.source.source())
            zoom_level = self._loading_options["zoom_level"]
            bounds = self._loading_options["bounds"]
            load_mask_layer = self._loading_options["load_mask_layer"]
            merge_tiles = self._loading_options["merge_tiles"]
            clip_tiles = self._loading_options["clip_tiles"]
            apply_styles = self._loading_options["apply_styles"]
            max_tiles = self._loading_options["max_tiles"]
            layer_filter = self._loading_options["layer_filter"]

            self.cancel_requested = False
            self.feature_collections_by_layer_name_and_geotype = {}
            self._qgis_layer_groups_by_name = {}
            self._update_progress(show_dialog=True, title="Loading '{}'".format(os.path.basename(self.source.name())))
            self._clip_tiles_at_tile_bounds = clip_tiles

            min_zoom = self.source.min_zoom()
            max_zoom = self.source.max_zoom()
            zoom_level = clamp(zoom_level, low=min_zoom, high=max_zoom)

            all_tiles = get_all_tiles(
                bounds=bounds,
                is_cancel_requested_handler=lambda: self.cancel_requested,
            )
            tiles_to_load = set()
            tiles = []
            tiles_to_ignore = set()
            for t in all_tiles:
                if self.cancel_requested or (max_tiles and len(tiles) >= max_tiles):
                    break

                file_name = self._get_tile_cache_name(zoom_level, t[0], t[1])
                tile = FileHelper.get_cached_tile(file_name)
                if tile and tile.decoded_data:
                    tiles.append(tile)
                    tiles_to_ignore.add((tile.column, tile.row))
                else:
                    tiles_to_load.add(t)

            remaining_nr_of_tiles = len(tiles_to_load)
            if max_tiles:
                if len(tiles) + len(tiles_to_load) >= max_tiles:
                    remaining_nr_of_tiles = max_tiles - len(tiles)
                    if remaining_nr_of_tiles < 0:
                        remaining_nr_of_tiles = 0
            debug("{} cache hits. {} may potentially be loaded.", len(tiles), remaining_nr_of_tiles)

            debug("Loading data for zoom level '{}' source '{}'", zoom_level, self.source.name())

            tile_data_tuples = []
            if remaining_nr_of_tiles > 0:
                tile_data_tuples = self.source.load_tiles(zoom_level=zoom_level,
                                                          tiles_to_load=tiles_to_load,
                                                          max_tiles=remaining_nr_of_tiles)
            if len(tiles) == 0 and (not tile_data_tuples or len(tile_data_tuples) == 0):
                QMessageBox.information(None, "No tiles found", "What a pity, no tiles could be found!")

            if load_mask_layer:
                mask_level = self.source.mask_level()
                if mask_level is not None and mask_level != zoom_level:
                    debug("Mapping {} tiles to mask level", len(all_tiles))
                    scheme = self.source.scheme()
                    mask_tiles = map(
                        lambda t: change_zoom(zoom_level, int(mask_level), t, scheme),
                        all_tiles)
                    debug("Mapping done")

                    mask_tiles_to_load = set()
                    for t in mask_tiles:
                        file_name = self._get_tile_cache_name(mask_level, t[0], t[1])
                        tile = FileHelper.get_cached_tile(file_name)
                        if tile and tile.decoded_data:
                            tiles.append(tile)
                        else:
                            mask_tiles_to_load.add(t)

                    debug("Loading mask layer (zoom_level={})", mask_level)
                    tile_data_tuples = []
                    if len(mask_tiles_to_load) > 0:
                        mask_layer_data = self.source.load_tiles(zoom_level=mask_level,
                                                                 tiles_to_load=mask_tiles_to_load,
                                                                 max_tiles=max_tiles)
                        debug("Mask layer loaded")
                        tile_data_tuples.extend(mask_layer_data)

            if tile_data_tuples and len(tile_data_tuples) > 0:
                if not self.cancel_requested:
                    decoded_tiles = self._decode_tiles(tile_data_tuples)
                    tiles.extend(decoded_tiles)
            if len(tiles) > 0:
                if not self.cancel_requested:
                    self._process_tiles(tiles, layer_filter)
                if not self.cancel_requested:
                    self._create_qgis_layers(merge_features=merge_tiles,
                                             apply_styles=apply_styles)

            self._update_progress(show_dialog=False)
            if self.cancel_requested:
                info("Import cancelled")
                self.cancelled.emit()
            else:
                info("Import complete")
                loaded_tiles_x = map(lambda t: t.coord()[0], tiles)
                loaded_tiles_y = map(lambda t: t.coord()[1], tiles)
                if len(loaded_tiles_x) == 0 or len(loaded_tiles_y) == 0:
                    return None

                loaded_extent = {"x_min": int(min(loaded_tiles_x)),
                                 "x_max": int(max(loaded_tiles_x)),
                                 "y_min": int(min(loaded_tiles_y)),
                                 "y_max": int(max(loaded_tiles_y)),
                                 "zoom": int(zoom_level)
                                 }
                loaded_extent["width"] = loaded_extent["x_max"] - loaded_extent["x_min"] + 1
                loaded_extent["height"] = loaded_extent["y_max"] - loaded_extent["y_min"] + 1
                self.loading_finished.emit(zoom_level, loaded_extent)
        except Exception as e:
            critical("An exception occured: {}, {}", e, traceback.format_exc())
            self.cancelled.emit()
    def _load_tiles(self,
                    options,
                    layers_to_load,
                    bounds=None,
                    ignore_limit=False):
        if self._debouncer.is_running():
            self._debouncer.pause()

        merge_tiles = options.merge_tiles_enabled()
        apply_styles = options.apply_styles_enabled()
        tile_limit = options.tile_number_limit()
        load_mask_layer = options.load_mask_layer_enabled()
        self._auto_zoom = options.auto_zoom_enabled()
        if ignore_limit:
            tile_limit = None
        manual_zoom = options.manual_zoom()
        clip_tiles = options.clip_tiles()

        if apply_styles:
            self._set_background_color()

        reader = self._current_reader
        if not reader:
            self._is_loading = False
        else:
            try:
                max_zoom = reader.source.max_zoom()
                min_zoom = reader.source.min_zoom()
                if self._auto_zoom:
                    zoom = self._get_zoom_for_current_map_scale()
                    zoom = clamp(zoom, low=min_zoom, high=max_zoom)
                else:
                    zoom = max_zoom
                    if manual_zoom is not None:
                        zoom = manual_zoom
                self._current_zoom = zoom

                source_bounds = reader.source.bounds_tile(zoom)
                if source_bounds and not self._extent_overlap_bounds(
                        bounds, source_bounds):
                    info(
                        "The current extent '{}' is not within the bounds of the source '{}'. The extent to load "
                        "will be set to the bounds of the source", bounds,
                        source_bounds)
                    bounds = source_bounds

                reader.set_options(layer_filter=layers_to_load,
                                   load_mask_layer=load_mask_layer,
                                   merge_tiles=merge_tiles,
                                   clip_tiles=clip_tiles,
                                   apply_styles=apply_styles,
                                   max_tiles=tile_limit)
                self._is_loading = True
                reader.load_tiles_async(zoom_level=zoom, bounds=bounds)
            except Exception as e:
                critical("An exception occured: {}", e)
                tb_lines = traceback.format_tb(sys.exc_traceback)
                tb_text = ""
                for line in tb_lines:
                    tb_text += line
                critical("{}", tb_text)
                self.iface.messageBar().pushMessage(
                    "Something went horribly wrong. Please have a look at the log.",
                    level=QgsMessageBar.CRITICAL,
                    duration=5)
                if self.progress_dialog:
                    self.progress_dialog.hide()
                self._is_loading = False