コード例 #1
0
ファイル: mpl_hsec.py プロジェクト: iamapickle/MSS
 def support_epsg_code(self, crs):
     """Returns a list of supported EPSG codes.
     """
     try:
         get_projection_params(crs)
     except ValueError:
         return False
     return True
コード例 #2
0
ファイル: topview.py プロジェクト: Marilyth/MSS
    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"],
                       "PROJECT_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()
コード例 #3
0
ファイル: test_utils.py プロジェクト: jukiforde/MSS
 def test_get_projection_params(self):
     assert utils.get_projection_params("epsg:4839") == {'basemap': {'epsg': '4839'}, 'bbox': 'meter(10.5,51)'}
     with pytest.raises(ValueError):
         utils.get_projection_params('auto2:42005')
     with pytest.raises(ValueError):
         utils.get_projection_params('auto:42001')
     with pytest.raises(ValueError):
         utils.get_projection_params('crs:84')
コード例 #4
0
    def produce_plot(self, query, mode):
        """
        Handler for a GetMap and GetVSec requests. Produces a plot with
        the parameters specified in the URL.

        # TODO: Handle multiple layers. (mr, 2010-06-09)
        # TODO: Cache the produced images: Check whether an image with the given
        #      parameters has already been produced. (mr, 2010-08-18)
        """
        logging.debug("GetMap/GetVSec request. Interpreting parameters..")

        # Evaluate query parameters:
        # =============================

        version = query.get("VERSION", "1.1.1")

        # Image size.
        width = query.get('WIDTH', 900)
        height = query.get('HEIGHT', 600)
        figsize = float(width if width != "" else 900), float(
            height if height != "" else 600)
        logging.debug("  requested image size = %sx%s", figsize[0], figsize[1])

        # Requested layers.
        layers = [
            layer for layer in query.get('LAYERS', '').strip().split(',')
            if layer
        ]
        layer = layers[0] if len(layers) > 0 else ''
        if layer.find(".") > 0:
            dataset, layer = layer.split(".")
        else:
            dataset = None
        logging.debug("  requested dataset = '%s', layer = '%s'", dataset,
                      layer)

        # Requested style(s).
        styles = [
            style
            for style in query.get('STYLES', 'default').strip().split(',')
            if style
        ]
        style = styles[0] if len(styles) > 0 else None
        logging.debug("  requested style = '%s'", style)

        # Forecast initialisation time.
        init_time = query.get('DIM_INIT_TIME')
        if init_time is not None:
            try:
                init_time = parse_iso_datetime(init_time)
            except ValueError:
                return self.create_service_exception(
                    code="InvalidDimensionValue",
                    text=
                    "DIM_INIT_TIME has wrong format (needs to be 2005-08-29T13:00:00Z)",
                    version=version)
        logging.debug("  requested initialisation time = '%s'", init_time)

        # Forecast valid time.
        valid_time = query.get('TIME')
        if valid_time is not None:
            try:
                valid_time = parse_iso_datetime(valid_time)
            except ValueError:
                return self.create_service_exception(
                    code="InvalidDimensionValue",
                    text=
                    "TIME has wrong format (needs to be 2005-08-29T13:00:00Z)",
                    version=version)
        logging.debug("  requested (valid) time = '%s'", valid_time)

        # Coordinate reference system.
        crs = query.get("CRS" if version == "1.3.0" else "SRS",
                        'EPSG:4326').lower()
        is_yx = version == "1.3.0" and crs.startswith("epsg") and int(
            crs[5:]) in axisorder_yx

        # Allow to request vertical sections via GetMap, if the specified CRS is of type "VERT:??".
        msg = None
        if crs.startswith('vert:logp'):
            mode = "getvsec"
        else:
            try:
                get_projection_params(crs)
            except ValueError:
                return self.create_service_exception(
                    code="InvalidSRS",
                    text=f"The requested CRS '{crs}' is not supported.",
                    version=version)
        logging.debug("  requested coordinate reference system = '%s'", crs)

        # Create a frameless figure (WMS) or one with title and legend
        # (MSS specific)? Default is WMS mode (frameless).
        noframe = query.get('FRAME', 'off').lower() == 'off'

        # Transparency.
        transparent = query.get('TRANSPARENT', 'false').lower() == 'true'
        if transparent:
            logging.debug("  requested transparent image")

        # Return format (image/png, text/xml, etc.).
        return_format = query.get('FORMAT', 'image/png').lower()
        logging.debug("  requested return format = '%s'", return_format)
        if return_format not in ["image/png", "text/xml"]:
            return self.create_service_exception(
                code="InvalidFORMAT",
                text=f"unsupported FORMAT: '{return_format}'",
                version=version)

        # 3) Check GetMap/GetVSec-specific parameters and produce
        #    the image with the corresponding section driver.
        # =======================================================
        if mode == "getmap":
            # Check requested layer.
            if (dataset not in self.hsec_layer_registry) or (
                    layer not in self.hsec_layer_registry[dataset]):
                return self.create_service_exception(
                    code="LayerNotDefined",
                    text=f"Invalid LAYER '{dataset}.{layer}' requested",
                    version=version)

            # Check if the layer requires time information and if they are given.
            if self.hsec_layer_registry[dataset][
                    layer].uses_inittime_dimension() and init_time is None:
                return self.create_service_exception(
                    code="MissingDimensionValue",
                    text=
                    "INIT_TIME not specified (use the DIM_INIT_TIME keyword)",
                    version=version)
            if self.hsec_layer_registry[dataset][
                    layer].uses_validtime_dimension() and valid_time is None:
                return self.create_service_exception(
                    code="MissingDimensionValue",
                    text="TIME not specified",
                    version=version)

            # Check if the requested coordinate system is supported.
            if not self.hsec_layer_registry[dataset][layer].support_epsg_code(
                    crs):
                return self.create_service_exception(
                    code="InvalidSRS",
                    text=f"The requested CRS '{crs}' is not supported.",
                    version=version)

            # Bounding box.
            try:
                if is_yx:
                    bbox = [
                        float(v) for v in query.get(
                            'BBOX', '-90,-180,90,180').split(',')
                    ]
                    bbox = (bbox[1], bbox[0], bbox[3], bbox[2])
                else:
                    bbox = [
                        float(v) for v in query.get(
                            'BBOX', '-180,-90,180,90').split(',')
                    ]

            except ValueError:
                return self.create_service_exception(
                    text=f"Invalid BBOX: {query.get('BBOX')}", version=version)

            # Vertical level, if applicable.
            level = query.get('ELEVATION')
            level = float(level) if level is not None else None
            layer_datatypes = self.hsec_layer_registry[dataset][
                layer].required_datatypes()
            if any(_x in layer_datatypes
                   for _x in ["pl", "al", "ml", "tl", "pv"]) and level is None:
                # Use the default value.
                level = -1
            elif ("sfc" in layer_datatypes) and \
                    all(_x not in layer_datatypes for _x in ["pl", "al", "ml", "tl", "pv"]) and \
                    level is not None:
                return self.create_service_exception(
                    text=
                    f"ELEVATION argument not applicable for layer '{layer}'. Please omit this argument.",
                    version=version)

            plot_driver = self.hsec_drivers[dataset]
            try:
                plot_driver.set_plot_parameters(
                    self.hsec_layer_registry[dataset][layer],
                    bbox=bbox,
                    level=level,
                    crs=crs,
                    init_time=init_time,
                    valid_time=valid_time,
                    style=style,
                    figsize=figsize,
                    noframe=noframe,
                    transparent=transparent,
                    return_format=return_format)
                image = plot_driver.plot()
            except (IOError, ValueError) as ex:
                logging.error("ERROR: %s %s", type(ex), ex)
                logging.debug("%s", traceback.format_exc())
                msg = "The data corresponding to your request is not available. Please check the " \
                      "times and/or levels you have specified.\n\n" \
                      f"Error message: '{ex}'"
                return self.create_service_exception(text=msg, version=version)

        elif mode == "getvsec":
            # Vertical secton path.
            path = query.get("PATH")
            if path is None:
                return self.create_service_exception(text="PATH not specified",
                                                     version=version)
            try:
                path = [float(v) for v in path.split(',')]
                path = [[lat, lon] for lat, lon in zip(path[0::2], path[1::2])]
            except ValueError:
                return self.create_service_exception(
                    text=f"Invalid PATH: {path}", version=version)
            logging.debug("VSEC PATH: %s", path)

            # Check requested layers.
            if (dataset not in self.vsec_layer_registry) or (
                    layer not in self.vsec_layer_registry[dataset]):
                return self.create_service_exception(
                    code="LayerNotDefined",
                    text=f"Invalid LAYER '{dataset}.{layer}' requested",
                    version=version)

            # Check if the layer requires time information and if they are given.
            if self.vsec_layer_registry[dataset][
                    layer].uses_inittime_dimension():
                if init_time is None:
                    return self.create_service_exception(
                        code="MissingDimensionValue",
                        text=
                        "INIT_TIME not specified (use the DIM_INIT_TIME keyword)",
                        version=version)
                if valid_time is None:
                    return self.create_service_exception(
                        code="MissingDimensionValue",
                        text="TIME not specified",
                        version=version)

            # Bounding box (num interp. points, p_bot, num labels, p_top).
            try:
                bbox = [
                    float(v)
                    for v in query.get("BBOX", "101,1050,10,180").split(",")
                ]
            except ValueError:
                return self.create_service_exception(
                    text=f"Invalid BBOX: {query.get('BBOX')}", version=version)

            plot_driver = self.vsec_drivers[dataset]
            try:
                plot_driver.set_plot_parameters(
                    plot_object=self.vsec_layer_registry[dataset][layer],
                    vsec_path=path,
                    vsec_numpoints=bbox[0],
                    vsec_path_connection="greatcircle",
                    vsec_numlabels=bbox[2],
                    init_time=init_time,
                    valid_time=valid_time,
                    style=style,
                    bbox=bbox,
                    figsize=figsize,
                    noframe=noframe,
                    transparent=transparent,
                    return_format=return_format)
                image = plot_driver.plot()
            except (IOError, ValueError) as ex:
                logging.error("ERROR: %s %s", type(ex), ex)
                msg = "The data corresponding to your request is not available. Please check the " \
                      "times and/or path you have specified.\n\n" \
                      f"Error message: {ex}"
                return self.create_service_exception(text=msg, version=version)

        # 4) Return the produced image.
        # =============================
        return image, return_format
コード例 #5
0
ファイル: mpl_hsec.py プロジェクト: iamapickle/MSS
    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(mss_wms_settings, "basemap_use_cache",
                                    False)
        basemap_request_size = getattr(mss_wms_settings,
                                       "basemap_request_size ", 200)
        basemap_cache_size = getattr(mss_wms_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
                    ]

        # Set up the map appearance.
        bm.drawcoastlines(color='0.25')
        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._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)
            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)

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