Exemplo n.º 1
0
 def support_epsg_code(self, crs):
     """
     Returns a list of supported EPSG codes.
     """
     try:
         get_projection_params(crs)
     except ValueError:
         return False
     return True
Exemplo n.º 2
0
    def changeMapSection(self, index=0, only_kwargs=False):
        """
        Change the current map section to one of the predefined regions.
        """
        # Get the initial projection parameters from the tables in mss_settings.
        current_map_key = self.cbChangeMapSection.currentText()
        predefined_map_sections = config_loader(
            dataset="predefined_map_sections")
        current_map = predefined_map_sections.get(current_map_key, {
            "CRS": current_map_key,
            "map": {}
        })
        proj_params = get_projection_params(current_map["CRS"])

        # Create a keyword arguments dictionary for basemap that contains
        # the projection parameters.
        kwargs = current_map["map"]
        kwargs.update({
            "CRS": current_map["CRS"],
            "BBOX_UNITS": proj_params["bbox"],
            "OPERATION_NAME": self.waypoints_model.name
        })
        kwargs.update(proj_params["basemap"])

        if only_kwargs:
            # Return kwargs dictionary and do NOT redraw the map.
            return kwargs

        logging.debug("switching to map section '%s' - '%s'", current_map_key,
                      kwargs)
        self.mpl.canvas.redraw_map(kwargs)
        self.mpl.navbar.clear_history()
Exemplo n.º 3
0
 def test_get_projection_params(self):
     assert coordinate.get_projection_params("epsg:4839") == {
         'basemap': {
             'epsg': '4839'
         },
         'bbox': 'meter(10.5,51)'
     }
     with pytest.raises(ValueError):
         coordinate.get_projection_params('auto2:42005')
     with pytest.raises(ValueError):
         coordinate.get_projection_params('auto:42001')
     with pytest.raises(ValueError):
         coordinate.get_projection_params('crs:84')
Exemplo n.º 4
0
    def plot_hsection(self,
                      data,
                      lats,
                      lons,
                      bbox=(-180, -90, 180, 90),
                      level=None,
                      figsize=(960, 640),
                      crs=None,
                      proj_params=None,
                      valid_time=None,
                      init_time=None,
                      style=None,
                      resolution=-1,
                      noframe=False,
                      show=False,
                      transparent=False):
        """
        EPSG overrides proj_params!
        """
        if proj_params is None:
            proj_params = {"projection": "cyl"}
            bbox_units = "latlon"
        # Projection parameters from EPSG code.
        if crs is not None:
            proj_params, bbox_units = [
                get_projection_params(crs)[_x] for _x in ("basemap", "bbox")
            ]

        logging.debug("plotting data..")

        # Check if required data is available.
        self.data_units = self.driver.data_units.copy()
        for datatype, dataitem, dataunit in self.required_datafields:
            if dataitem not in data:
                raise KeyError(f"required data field '{dataitem}' not found")
            origunit = self.driver.data_units[dataitem]
            if dataunit is not None:
                data[dataitem] = convert_to(data[dataitem], origunit, dataunit)
                self.data_units[dataitem] = dataunit
            else:
                logging.debug("Please add units to plot variables")

        # Copy parameters to properties.
        self.data = data
        self.lats = lats
        self.lons = lons
        self.level = level
        self.valid_time = valid_time
        self.init_time = init_time
        self.style = style
        self.resolution = resolution
        self.noframe = noframe
        self.crs = crs

        # Derive additional data fields and make the plot.
        logging.debug("preparing additional data fields..")
        self._prepare_datafields()

        logging.debug("creating figure..")
        dpi = 80
        figsize = (figsize[0] / dpi), (figsize[1] / dpi)
        facecolor = "white"
        fig = mpl.figure.Figure(figsize=figsize, dpi=dpi, facecolor=facecolor)
        logging.debug(
            "\twith frame and legends" if not noframe else "\twithout frame")
        if noframe:
            ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
        else:
            ax = fig.add_axes([0.05, 0.05, 0.9, 0.88])

        # The basemap instance is created with a fixed aspect ratio for framed
        # plots; the aspect ratio is not fixed for frameless plots (standard
        # WMS). This means that for WMS plots, the map will always fill the
        # entire image area, no matter of whether this stretches or shears the
        # map. This is the behaviour specified by the WMS standard (WMS Spec
        # 1.1.1, Section 7.2.3.8):
        # "The returned picture, regardless of its return format, shall have
        # exactly the specified width and height in pixels. In the case where
        # the aspect ratio of the BBOX and the ratio width/height are different,
        # the WMS shall stretch the returned map so that the resulting pixels
        # could themselves be rendered in the aspect ratio of the BBOX.  In
        # other words, it should be possible using this definition to request a
        # map for a device whose output pixels are themselves non-square, or to
        # stretch a map into an image area of a different aspect ratio."
        # NOTE: While the MSUI always requests image sizes that match the aspect
        # ratio, for instance the Metview 4 client does not (mr, 2011Dec16).

        # Some additional code to store the last 20 coastlines in memory for quicker
        # access.
        key = repr((proj_params, bbox, bbox_units))
        basemap_use_cache = getattr(mswms_settings, "basemap_use_cache", False)
        basemap_request_size = getattr(mswms_settings, "basemap_request_size ",
                                       200)
        basemap_cache_size = getattr(mswms_settings, "basemap_cache_size", 20)
        bm_params = {
            "area_thresh": 1000.,
            "ax": ax,
            "fix_aspect": (not noframe)
        }
        bm_params.update(proj_params)
        if bbox_units == "degree":
            bm_params.update({
                "llcrnrlon": bbox[0],
                "llcrnrlat": bbox[1],
                "urcrnrlon": bbox[2],
                "urcrnrlat": bbox[3]
            })
        elif bbox_units.startswith("meter"):
            # convert meters to degrees
            try:
                bm_p = basemap.Basemap(resolution=None, **bm_params)
            except ValueError:  # projection requires some extent
                bm_p = basemap.Basemap(resolution=None,
                                       width=1e7,
                                       height=1e7,
                                       **bm_params)
            bm_center = [float(_x) for _x in bbox_units[6:-1].split(",")]
            center_x, center_y = bm_p(*bm_center)
            bbox_0, bbox_1 = bm_p(bbox[0] + center_x,
                                  bbox[1] + center_y,
                                  inverse=True)
            bbox_2, bbox_3 = bm_p(bbox[2] + center_x,
                                  bbox[3] + center_y,
                                  inverse=True)
            bm_params.update({
                "llcrnrlon": bbox_0,
                "llcrnrlat": bbox_1,
                "urcrnrlon": bbox_2,
                "urcrnrlat": bbox_3
            })
        elif bbox_units == "no":
            pass
        else:
            raise ValueError(f"bbox_units '{bbox_units}' not known.")
        if basemap_use_cache and key in BASEMAP_CACHE:
            bm = basemap.Basemap(resolution=None, **bm_params)
            (bm.resolution, bm.coastsegs, bm.coastpolygontypes,
             bm.coastpolygons, bm.coastsegs, bm.landpolygons, bm.lakepolygons,
             bm.cntrysegs) = BASEMAP_CACHE[key]
            logging.debug("Loaded '%s' from basemap cache", key)
        else:
            bm = basemap.Basemap(resolution='l', **bm_params)
            # read in countries manually, as those are laoded only on demand
            bm.cntrysegs, _ = bm._readboundarydata("countries")
            if basemap_use_cache:
                BASEMAP_CACHE[key] = (bm.resolution, bm.coastsegs,
                                      bm.coastpolygontypes, bm.coastpolygons,
                                      bm.coastsegs, bm.landpolygons,
                                      bm.lakepolygons, bm.cntrysegs)
        if basemap_use_cache:
            BASEMAP_REQUESTS.append(key)
            BASEMAP_REQUESTS[:] = BASEMAP_REQUESTS[-basemap_request_size:]

            if len(BASEMAP_CACHE) > basemap_cache_size:
                useful = {}
                for idx, key in enumerate(BASEMAP_REQUESTS):
                    useful[key] = useful.get(key, 0) + idx
                least_useful = sorted([
                    (value, key) for key, value in useful.items()
                ])[:-basemap_cache_size]
                for _, key in least_useful:
                    del BASEMAP_CACHE[key]
                    BASEMAP_REQUESTS[:] = [
                        _x for _x in BASEMAP_REQUESTS if key != _x
                    ]

        if self._plot_countries:
            # Set up the map appearance.
            try:
                bm.drawcoastlines(color='0.25')
            except ValueError as ex:
                logging.error(
                    "Error in basemap/matplotlib call of drawcoastlines: %s",
                    ex)
            bm.drawcountries(color='0.5')
            bm.drawmapboundary(fill_color='white')

            # zorder = 0 is necessary to paint over the filled continents with
            # scatter() for drawing the flight tracks and trajectories.
            # Curiously, plot() works fine without this setting, but scatter()
            # doesn't.
            bm.fillcontinents(color='0.98', lake_color='white', zorder=0)
            self._draw_auto_graticule(bm)

        if noframe:
            ax.axis('off')

        self.bm = bm  # !! BETTER PASS EVERYTHING AS PARAMETERS?
        self.fig = fig
        self.shift_data()
        self.mask_data()
        self.lonmesh, self.latmesh = bm(*np.meshgrid(self.lons, self.lats))
        self._plot_style()

        # Set transparency for the output image.
        if transparent:
            fig.patch.set_alpha(0.)

        # Return the image as png embedded in a StringIO stream.
        canvas = FigureCanvas(fig)
        output = io.BytesIO()
        canvas.print_png(output)

        if show:
            logging.debug("saving figure to mpl_hsec.png ..")
            canvas.print_png("mpl_hsec.png")

        # Convert the image to an 8bit palette image with a significantly
        # smaller file size (~factor 4, from RGBA to one 8bit value, plus the
        # space to store the palette colours).
        # NOTE: PIL at the current time can only create an adaptive palette for
        # RGB images, hence alpha values are lost here. If transparency is
        # requested, the figure face colour is stored as the "transparent"
        # colour in the image. This works in most cases, but might lead to
        # visible artefacts in some cases.
        logging.debug("converting image to indexed palette.")
        # Read the above stored png into a PIL image and create an adaptive
        # colour palette.
        output.seek(0)  # necessary for PIL.Image.open()
        palette_img = PIL.Image.open(output).convert(mode="RGB").convert(
            "P", palette=PIL.Image.ADAPTIVE)
        output = io.BytesIO()
        if not transparent:
            logging.debug("saving figure as non-transparent PNG.")
            palette_img.save(
                output,
                format="PNG")  # using optimize=True doesn't change much
        else:
            # If the image has a transparent background, we need to find the
            # index of the background colour in the palette. See the
            # documentation for PIL's ImagePalette module
            # (http://www.pythonware.com/library/pil/handbook/imagepalette.htm). The
            # idea is to create a 256 pixel image with the same colour palette
            # as the original image and use it as a lookup-table. Converting the
            # lut image back to RGB gives us a list of all colours in the
            # palette. (Why doesn't PIL provide a method to directly access the
            # colours in a palette??)
            lut = palette_img.resize((256, 1))
            lut.putdata(list(range(256)))
            lut = [c[1] for c in lut.convert("RGB").getcolors()]
            facecolor_rgb = list(
                mpl.colors.hex2color(mpl.colors.cnames[facecolor]))
            for i in [0, 1, 2]:
                facecolor_rgb[i] = int(facecolor_rgb[i] * 255)
            try:
                facecolor_index = lut.index(tuple(facecolor_rgb))
                logging.debug(
                    "saving figure as transparent PNG with transparency index %s.",
                    facecolor_index)
                palette_img.save(output,
                                 format="PNG",
                                 transparency=facecolor_index)
            except ValueError:
                logging.debug(
                    "transparency requested but not possible, saving non-transparent instead"
                )
                palette_img.save(output, format="PNG")

        logging.debug("returning figure..")
        return output.getvalue()