[Git][debian-gis-team/pycoast][upstream] New upstream version 1.4.0+dfsg

Antonio Valentino gitlab at salsa.debian.org
Thu Jun 11 07:09:23 BST 2020



Antonio Valentino pushed to branch upstream at Debian GIS Project / pycoast


Commits:
2ec45a39 by Antonio Valentino at 2020-06-11T05:11:37+00:00
New upstream version 1.4.0+dfsg
- - - - -


19 changed files:

- .gitignore
- AUTHORS.md
- CHANGELOG.md
- + docs/source/images/nh_points_agg.png
- docs/source/index.rst
- + docs/source/points.rst
- pycoast/__init__.py
- pycoast/cw_agg.py
- pycoast/cw_base.py
- pycoast/cw_pil.py
- pycoast/tests/coasts_and_grid_agg.ini
- + pycoast/tests/grid_nh_cfg_agg.png
- + pycoast/tests/nh_points_agg.ini
- + pycoast/tests/nh_points_agg.png
- + pycoast/tests/nh_points_cfg_pil.png
- + pycoast/tests/nh_points_pil.ini
- + pycoast/tests/nh_points_pil.png
- pycoast/tests/test_pycoast.py
- pycoast/version.py


Changes:

=====================================
.gitignore
=====================================
@@ -58,5 +58,3 @@ docs/_build/
 
 # PyBuilder
 target/
-
-


=====================================
AUTHORS.md
=====================================
@@ -15,3 +15,4 @@ The following people have made contributions to this project:
 - [Martin Raspaud (mraspaud)](https://github.com/mraspaud)
 - [Hrobjartur Thorsteinsson (thorsteinssonh)](https://github.com/thorsteinssonh)
 - [Antonio Valentino (avalentino)](https://github.com/avalentino)
+- [Yufei Zhu (yufeizhu600)](https://github.com/yufeizhu600)


=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,39 @@
+## Version 1.4.0 (2020/06/08)
+
+### Issues Closed
+
+* [Issue 36](https://github.com/pytroll/pycoast/issues/36) - Add points to an image with list of lat/long pairs ([PR 42](https://github.com/pytroll/pycoast/pull/42))
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 45](https://github.com/pytroll/pycoast/pull/45) - Fix cached overlays always being regenerated
+* [PR 44](https://github.com/pytroll/pycoast/pull/44) - Fix adding textbox in the add_points method
+
+In this release 2 pull requests were closed.
+
+
+## Version 1.3.2 (2019/12/06)
+
+### Issues Closed
+
+* [Issue 39](https://github.com/pytroll/pycoast/issues/39) - distorted/strange coastlines with pyproj-2.4.2 ([PR 40](https://github.com/pytroll/pycoast/pull/40))
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 40](https://github.com/pytroll/pycoast/pull/40) - Fix compatibility with pyproj 2.4.2 and reduce generated warnings ([39](https://github.com/pytroll/pycoast/issues/39))
+* [PR 38](https://github.com/pytroll/pycoast/pull/38) - Remove unnecessary casting in adding overlay from dictionary
+
+In this release 2 pull requests were closed.
+
+
 ## Version 1.3.1 (2019/11/07)
 
 ### Pull Requests Merged


=====================================
docs/source/images/nh_points_agg.png
=====================================
Binary files /dev/null and b/docs/source/images/nh_points_agg.png differ


=====================================
docs/source/index.rst
=====================================
@@ -20,6 +20,7 @@ images using data from the GSHHS and WDBII datasets
    graticule
    config
    polygons_and_lines
+   points
    shapefiles
    test
    PyCoast API <api/pycoast>


=====================================
docs/source/points.rst
=====================================
@@ -0,0 +1,88 @@
+Add custom points with descriptions
+-----------------------------------
+
+Pycoast can add a symbol to points of interest on an image. The following examples show how
+we might use the :meth:`~pycoast.cw_agg.ContourWriterAGG.add_points` method to annotate the
+points on an image.
+
+First of all, we setup a PIL image with an area definition, then we add coastlines and
+borders for reference.
+
+    >>> from PIL import Image
+    >>> from pycoast import ContourWriterAGG
+    >>> img = Image.new('RGB', (1024, 1024), (255, 255, 255))
+    >>> proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
+    >>> area_extent = (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)
+    >>> area_def = AreaDefinition('nh', 'nh', 'nh', proj4_string, 1024, 1024, area_extent)
+    >>> cw = ContourWriterAGG('/home/esn/data/gshhs')
+    >>> cw.add_coastlines(img, area_def, outline='black', resolution='l', level=4)
+    >>> cw.add_borders(img, area_def, outline='black', width=3, level=1, resolution='c')
+
+Now we can add a circle, which is the default symbol, with default point size 6 at the
+location of Berlin, the name of the location will marked in a text box with black borders
+and the default text size is 12.
+
+    >>> points_list = [((13.4050, 52.5200), 'Berlin')]
+    >>> cw.add_points(pil_img, area, points_list=points_list, font_file=font_file)
+
+We can also annotate the image with text only by setting the ptsize to 0.
+The example below will add 'Rome' at the given location without a symbol.
+
+    >>> points_list = [((12.4964, 41.9028), 'Rome')]
+    >>> cw.add_points(pil_img, area, points_list=points_list,
+    ...               font_file=font_file, font_size=16,
+    ...               symbol='circle', ptsize=0,
+    ...               box_outline='black', text_linewidth=1,
+    ...               box_fill='yellow', box_opacity=200)
+
+Similarly, assign the description as an empty string will only draw the symbol on the image.
+The example below will draw a square symbol at the location of Paris.
+
+    >>> points_list = [((2.3522, 48.8566), '')]
+    >>> cw.add_points(pil_img, area, points_list=points_list,
+    ...               font_file=font_file,
+    ...               symbol='square', ptsize=10,
+    ...               outline='red', width=1,
+    ...               fill='blue', fill_opacity=128)
+
+Finally, we can fully customize the annotation as the example below, which will add
+a circle in black with line width set to 2 and filled in red color with opacity equals 255;
+the description will be 'London' in a textbox with blue borders and filled with green color
+with opacity set to 128.
+
+    >>> points_list = [((0.1278, 51.5074), 'London')]
+    >>> cw.add_points(img, area_def, points_list=points_list,
+    ...               font_file=font_file, font_size=14,
+    ...               symbol='circle', ptsize=14,
+    ...               outline='black', width=2,
+    ...               fill='red', fill_opacity=255,
+    ...               box_outline='blue', box_linewidth=1.5,
+    ...               box_fill='green', box_opacity=128)
+    >>> img.show()
+
+.. image:: images/nh_points_agg.png
+
+
+Please check out the docstrings of the :meth:`~pycoast.cw_agg.ContourWriterAGG.add_points`
+function for the full description of the parameters.
+
+Moreover, we can organize the overlay parameters in a dictionary and use :meth:`~pycoast.cw_agg.ContourWriterAGG.add_overlay_from_dict`
+to apply the overlays in one shot.
+
+    >>> points = {'points_list': [((2.3522, 48.8566), 'Paris'), ((0.1278, 51.5074), 'London')],
+    ...           'font': font_file,
+    ...           'symbol': 'circle', 'ptsize': 16,
+    ...           'outline': 'black', 'width': 3,
+    ...           'fill': 'red', 'fill_opacity': 128,
+    ...           'box_outline': 'blue', 'box_linewidth': 0.5,
+    ...           'box_fill': 'yellow', 'box_opacity': 200}
+
+    >>> overlays = {'coasts': {'outline': 'black', 'level': 4, 'resolution': 'l'},
+    ...             'borders': {'outline': 'black', 'width': 3, 'level': 1, 'resolution': 'c'},
+    ...             'points': points}
+
+    >>> img = cw.add_overlay_from_dict(overlays, area_def)
+
+    >>> img.save('./add_overlays_from_dict.png')
+
+.. _PIL: http://www.pythonware.com/products/pil/


=====================================
pycoast/__init__.py
=====================================
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
 from .cw_pil import ContourWriterPIL
 from .cw_agg import ContourWriterAGG
 from pycoast.cw_base import get_resolution_from_area
@@ -8,6 +7,7 @@ from .version import get_versions
 __version__ = get_versions()['version']
 del get_versions
 
+
 class ContourWriter(ContourWriterPIL):
     """Writer wrapper for deprecation warning.
 


=====================================
pycoast/cw_agg.py
=====================================
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 # pycoast, Writing of coastlines, borders and rivers to images in Python
 #
-# Copyright (C) 2011-2018 PyCoast Developers
+# Copyright (C) 2011-2020 PyCoast Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -52,32 +52,39 @@ class ContourWriterAGG(ContourWriterBase):
         pen = aggdraw.Pen(kwargs['outline'],
                           kwargs['width'],
                           kwargs['outline_opacity'])
-        if kwargs['fill'] is None:
-            fill_opacity = 0
-        else:
-            fill_opacity = kwargs['fill_opacity']
+
+        fill_opacity = kwargs.get('fill_opacity', 255) if kwargs['fill'] else 0
+
         brush = aggdraw.Brush(kwargs['fill'], fill_opacity)
         draw.polygon(coordinates, pen, brush)
 
     def _draw_rectangle(self, draw, coordinates, **kwargs):
         """Draw rectangle."""
-        pen = aggdraw.Pen(kwargs['outline'])
+        pen = aggdraw.Pen(kwargs['outline'],
+                          kwargs['width'],
+                          kwargs['outline_opacity'])
+
+        fill_opacity = kwargs.get('fill_opacity', 255) if kwargs['fill'] else 0
 
-        fill_opacity = kwargs.get('fill_opacity', 255)
         brush = aggdraw.Brush(kwargs['fill'], fill_opacity)
+
         draw.rectangle(coordinates, pen, brush)
 
     def _draw_ellipse(self, draw, coordinates, **kwargs):
         """Draw ellipse."""
-        pen = aggdraw.Pen(kwargs['outline'])
+        pen = aggdraw.Pen(kwargs['outline'],
+                          kwargs['width'],
+                          kwargs['outline_opacity'])
+
+        fill_opacity = kwargs.get('fill_opacity', 255) if kwargs['fill'] else 0
 
-        fill_opacity = kwargs.get('fill_opacity', 255)
         brush = aggdraw.Brush(kwargs['fill'], fill_opacity)
+
         draw.ellipse(coordinates, brush, pen)
 
     def _draw_text_box(self, draw, text_position, text, font, outline,
-                       box_outline, box_opacity):
-        """Add text box in xy."""
+                       box_outline, box_opacity, **kwargs):
+        """Add a text box at position (x,y)."""
 
         if box_outline is not None:
             text_size = draw.textsize(text, font)
@@ -88,9 +95,12 @@ class ContourWriterAGG(ContourWriterBase):
             yLR = yUL + text_size[1]
             box_size = (xUL, yUL, xLR, yLR)
 
+            width = kwargs.get('box_linewidth', 1)
+            fill = kwargs.get('box_fill', None)
+
             self._draw_rectangle(
-                draw, box_size, fill=box_outline, fill_opacity=box_opacity,
-                outline=box_outline)
+                draw, box_size, outline=box_outline, width=width,
+                outline_opacity=box_opacity, fill=fill, fill_opacity=box_opacity)
 
         self._draw_text(draw, text_position, text, font, align="no")
 
@@ -101,6 +111,40 @@ class ContourWriterAGG(ContourWriterBase):
                           kwargs['outline_opacity'])
         draw.line(coordinates, pen)
 
+    def _draw_asterisk(self, draw, pt_size, coordinate, **kwargs):
+        """Draw a asterisk sign '*' at the given coordinate. """
+        half_ptsize = int(round(pt_size / 2.))
+        x, y = coordinate
+
+        outline = kwargs.get('outline', 'white')
+        width = kwargs.get('width', 1.)
+        outline_opacity = kwargs.get('outline_opacity', 255)
+
+        # draw '|'
+        (x_bm, y_bm) = (x, y - half_ptsize)  # bottom middle point
+        (x_tm, y_tm) = (x, y + half_ptsize)  # top middle point
+        self._draw_line(draw, [(x_bm, y_bm), (x_tm, y_tm)],
+                        outline=outline, width=width,
+                        outline_opacity=outline_opacity)
+        # draw '-'
+        (x_lm, y_lm) = (x - half_ptsize, y)  # left middle point
+        (x_rm, y_rm) = (x + half_ptsize, y)  # right middle point
+        self._draw_line(draw, [(x_lm, y_lm), (x_rm, y_rm)],
+                        outline=outline, width=width,
+                        outline_opacity=outline_opacity)
+        # draw '/'
+        (x_bl, y_bl) = (x - half_ptsize, y - half_ptsize)  # bottom left point
+        (x_tr, y_tr) = (x + half_ptsize, y + half_ptsize)  # top right point
+        self._draw_line(draw, [(x_bl, y_bl), (x_tr, y_tr)],
+                        outline=outline, width=width,
+                        outline_opacity=outline_opacity)
+        # draw '\'
+        (x_tl, y_tl) = (x - half_ptsize, y + half_ptsize)  # top left point
+        (x_br, y_br) = (x + half_ptsize, y - half_ptsize)  # bottom right point
+        self._draw_line(draw, [(x_tl, y_tl), (x_br, y_br)],
+                        outline=outline, width=width,
+                        outline_opacity=outline_opacity)
+
     def _finalize(self, draw):
         """Flush the AGG image object."""
         draw.flush()
@@ -274,7 +318,7 @@ class ContourWriterAGG(ContourWriterBase):
                  minor_outline='white', minor_width=0.5,
                  minor_outline_opacity=255, minor_is_tick=True,
                  lon_placement='tb', lat_placement='lr'):
-        """Add a lon-lat grid to a PIL image object
+        """Add a lon-lat grid to a PIL image object.
 
         :Parameters:
             image : object
@@ -520,7 +564,7 @@ class ContourWriterAGG(ContourWriterBase):
 
         """
         image = Image.open(filename)
-        image = image.convert("RGBA") 
+        image = image.convert("RGBA")
         self.add_borders(image, area_def, resolution=resolution, level=level,
                          outline=outline, width=width,
                          outline_opacity=outline_opacity, x_offset=x_offset,
@@ -592,7 +636,7 @@ class ContourWriterAGG(ContourWriterBase):
         """
 
         image = Image.open(filename)
-        image = image.convert("RGBA") 
+        image = image.convert("RGBA")
         self.add_rivers(image, area_def, resolution=resolution, level=level,
                         outline=outline, width=width,
                         outline_opacity=outline_opacity, x_offset=x_offset,


=====================================
pycoast/cw_base.py
=====================================
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 # pycoast, Writing of coastlines, borders and rivers to images in Python
 #
-# Copyright (C) 2011-2018 PyCoast Developers
+# Copyright (C) 2011-2020 PyCoast Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -557,7 +557,10 @@ class ContourWriterBase(object):
         prj = Proj(proj_def)
 
         # Calculate min and max lons and lats of interest
-        lon_min, lon_max, lat_min, lat_max = _get_lon_lat_bounding_box(area_extent, x_size, y_size, prj)
+        lon_min, lon_max, lat_min, lat_max = _get_lon_lat_bounding_box(area_extent,
+                                                                       x_size,
+                                                                       y_size,
+                                                                       prj)
 
         # Iterate through shapes
         for shape in shapes:
@@ -695,7 +698,7 @@ class ContourWriterBase(object):
             logger.error("Error in %s", str(config_file))
             raise
 
-        SECTIONS = ['cache', 'coasts', 'rivers', 'borders', 'cities', 'grid']
+        SECTIONS = ['cache', 'coasts', 'rivers', 'borders', 'cities', 'points', 'grid']
         overlays = {}
         for section in config.sections():
             if section in SECTIONS:
@@ -709,7 +712,8 @@ class ContourWriterBase(object):
         return overlays
 
     def add_overlay_from_dict(self, overlays, area_def, cache_epoch=None, background=None):
-        """Create and return a transparent image adding all the overlays contained in the `overlays` dict.
+        """Create and return a transparent image adding all the overlays contained in
+           the `overlays` dict.
 
         :Parameters:
             overlays : dict
@@ -717,24 +721,31 @@ class ContourWriterBase(object):
             area_def : object
                 Area Definition of the creating image
             cache_epoch: seconds since epoch
-                The latest time allowed for cache the cache file. If the cache file is older than this (mtime),
-                the cache should be regenerated.
+                The latest time allowed for cache the cache file. If the cache
+                file is older than this (mtime), the cache should be
+                regenerated. Defaults to 0 meaning always reuse the cached
+                file if it exists. Requires "cache" to be configured in the
+                provided dictionary (see below).
             background: pillow image instance
                 The image on which to write the overlays on. If it's None (default),
-                a new image is created, otherwise the provide background is use
-                an change *in place*.
+                a new image is created, otherwise the provide background is
+                used and changed *in place*.
 
 
             The keys in `overlays` that will be taken into account are:
-            cache, coasts, rivers, borders, cities, grid
+            cache, coasts, rivers, borders, cities, points, grid
 
-            For all of them except `cache`, the items are the same as the corresponding
-            functions in pycoast, so refer to the docstrings of these functions
-            (add_coastlines, add_rivers, add_borders, add_grid, add_cities).
-            For cache, two parameters are configurable: `file` which specifies the directory
-            and the prefix of the file to save the caches decoration to
-            (for example /var/run/black_coasts_red_borders), and `regenerate` that can be
-            True or False (default) to force the overwriting of an already cached file.
+            For all of them except `cache`, the items are the same as the
+            corresponding functions in pycoast, so refer to the docstrings of
+            these functions (add_coastlines, add_rivers, add_borders,
+            add_grid, add_cities, add_points). For cache, two parameters are
+            configurable:
+
+            - `file`: specify the directory and the prefix
+                  of the file to save the caches decoration to (for example
+                  /var/run/black_coasts_red_borders)
+            - `regenerate`: True or False (default) to force the overwriting
+                  of an already cached file.
 
         """
 
@@ -745,7 +756,7 @@ class ContourWriterBase(object):
                           area_def.area_id + '.png')
 
             try:
-                config_time = cache_epoch
+                config_time = cache_epoch or 0
                 cache_time = os.path.getmtime(cache_file)
                 # Cache file will be used only if it's newer than config file
                 if ((config_time is not None and config_time < cache_time)
@@ -767,6 +778,9 @@ class ContourWriterBase(object):
         else:
             foreground = Image.new('RGBA', (x_size, y_size), (0, 0, 0, 0))
 
+        is_agg = self._draw_module == "AGG"
+
+        # Coasts, rivers, borders
         default_resolution = get_resolution_from_area(area_def)
 
         DEFAULT = {'level': 1,
@@ -779,14 +793,10 @@ class ContourWriterBase(object):
                    'y_offset': 0,
                    'resolution': default_resolution}
 
-        is_agg = self._draw_module == "AGG"
-
-        # Coasts, rivers, borders
         for section, fun in zip(['coasts', 'rivers', 'borders'],
                                 [self.add_coastlines,
                                  self.add_rivers,
                                  self.add_borders]):
-
             if section in overlays:
 
                 params = DEFAULT.copy()
@@ -821,7 +831,30 @@ class ContourWriterBase(object):
             self.add_cities(foreground, area_def, citylist, font_file,
                             font_size, pt_size, outline, box_outline,
                             box_opacity)
+        # Points management
+        if 'points' in overlays:
+            DEFAULT_FONTSIZE = 12
+            DEFAULT_SYMBOL = 'circle'
+            DEFAULT_PTSIZE = 6
+            DEFAULT_OUTLINE = 'white'
+            DEFAULT_FILL = None
+
+            params = overlays['points'].copy()
 
+            points_list = list(params.pop('points_list'))
+            font_file = params.pop('font')
+            font_size = int(params.pop('font_size', DEFAULT_FONTSIZE))
+
+            symbol = params.pop('symbol', DEFAULT_SYMBOL)
+            ptsize = int(params.pop('ptsize', DEFAULT_PTSIZE))
+
+            outline = params.pop('outline', DEFAULT_OUTLINE)
+            fill = params.pop('fill', DEFAULT_FILL)
+
+            self.add_points(foreground, area_def, points_list, font_file, font_size,
+                            symbol, ptsize, outline, fill, **params)
+
+        # Grids overlay
         if 'grid' in overlays:
             lon_major = float(overlays['grid'].get('lon_major', 10.0))
             lat_major = float(overlays['grid'].get('lat_major', 10.0))
@@ -867,7 +900,8 @@ class ContourWriterBase(object):
         return foreground
 
     def add_overlay_from_config(self, config_file, area_def, background=None):
-        """Create and return a transparent image adding all the overlays contained in a configuration file.
+        """Create and return a transparent image adding all the overlays contained in
+           a configuration file.
 
         :Parameters:
             config_file : str
@@ -877,7 +911,8 @@ class ContourWriterBase(object):
 
         """
         overlays = self._config_to_dict(config_file)
-        return self.add_overlay_from_dict(overlays, area_def, os.path.getmtime(config_file), background)
+        return self.add_overlay_from_dict(overlays, area_def,
+                                          os.path.getmtime(config_file), background)
 
     def add_cities(self, image, area_def, citylist, font_file, font_size,
                    ptsize, outline, box_outline, box_opacity, db_root_path=None):
@@ -888,6 +923,7 @@ class ContourWriterBase(object):
             db_root_path = self.db_root_path
         if db_root_path is None:
             raise ValueError("'db_root_path' must be specified to use this method")
+
         draw = self._get_canvas(image)
 
         # read shape file with points
@@ -924,24 +960,138 @@ class ContourWriterBase(object):
                 except ValueError as exc:
                     logger.debug("Point not added (%s)", str(exc))
                 else:
-
                     # add_dot
                     if ptsize is not None:
                         dot_box = [x - ptsize, y - ptsize,
                                    x + ptsize, y + ptsize]
                         self._draw_ellipse(
                             draw, dot_box, fill=outline, outline=outline)
-                        text_position = [x + 9, y - 5]  # FIX ME
+                        text_position = [x + 9, y - 5]  # FIXME
                     else:
                         text_position = [x, y]
 
                 # add text_box
-                    self._draw_text_box(draw, text_position, city_name, font,
-                                        outline, box_outline, box_opacity)
+                    self._draw_text_box(draw, text_position, city_name, font, outline,
+                                        box_outline, box_opacity)
                     logger.info("%s added", str(city_name))
 
         self._finalize(draw)
 
+    def add_points(self, image, area_def, points_list, font_file, font_size=12,
+                   symbol='circle', ptsize=6, outline='black', fill='white', **kwargs):
+        """Add a symbol and/or text at the point(s) of interest to a PIL image object.
+
+        :Parameters:
+            image : object
+                PIL image object
+            area_def : object
+                Area Definition of the provided image
+            points_list : list [((lon, lat), desc)]
+              | a list of points defined with (lon, lat) in float and a desc in string
+              | [((lon1, lat1), desc1), ((lon2, lat2), desc2)]
+              | lon : float
+              |    longitude of a point
+              | lat : float
+              |    latitude of a point
+              | desc : str
+              |    description of a point
+            font_file : str
+                Path to font file
+            font_size : int
+                Size of font
+            symbol : string
+                type of symbol, one of the elelment from the list
+                ['circle', 'square', 'asterisk']
+            ptsize : int
+                Size of the point.
+            outline : str or (R, G, B), optional
+                Line color of the symbol
+            fill : str or (R, G, B), optional
+                Filling color of the symbol
+
+        :Optional keyword arguments:
+            width : float
+                Line width of the symbol
+            outline_opacity : int, optional {0; 255}
+                Opacity of the line of the symbol.
+            fill_opacity : int, optional {0; 255}
+                Opacity of the filling of the symbol
+            box_outline : str or (R, G, B), optional
+                Line color of the textbox borders.
+            box_linewidth : float
+                Line width of the the borders of the textbox
+            box_fill : str or (R, G, B), optional
+                Filling color of the background of the textbox.
+            box_opacity : int, optional {0; 255}
+                Opacity of the background filling of the textbox.
+        """
+        try:
+            from pyresample.geometry import AreaDefinition
+        except ImportError:
+            raise ImportError("Missing required 'pyresample' module, please install it.")
+
+        if not isinstance(area_def, AreaDefinition):
+            raise ValueError("Expected 'area_def' is an instance of AreaDefinition object")
+
+        draw = self._get_canvas(image)
+
+        # Iterate through points list
+        for point in points_list:
+            (lon, lat), desc = point
+            try:
+                x, y = area_def.get_xy_from_lonlat(lon, lat)
+            except ValueError:
+                logger.info("Point %s is out of the area, it will not be added to the image.",
+                            str((lon, lat)))
+            else:
+                if ptsize != 0:
+                    half_ptsize = int(round(ptsize / 2.))
+
+                    dot_box = [x - half_ptsize, y - half_ptsize,
+                               x + half_ptsize, y + half_ptsize]
+
+                    width = kwargs.get('width', 1.)
+                    outline_opacity = kwargs.get('outline_opacity', 255)
+                    fill_opacity = kwargs.get('fill_opacity', 0)
+
+                    # draw the symbol at the (x, y) position
+                    if symbol == 'circle':  # a 'circle' or a 'dot' i.e circle with fill
+                        self._draw_ellipse(draw, dot_box,
+                                           outline=outline, width=width,
+                                           outline_opacity=outline_opacity,
+                                           fill=fill, fill_opacity=fill_opacity)
+                    elif symbol == 'square':
+                        self._draw_rectangle(draw, dot_box,
+                                             outline=outline, width=width,
+                                             outline_opacity=outline_opacity,
+                                             fill=fill, fill_opacity=fill_opacity)
+                    elif symbol == 'asterisk':  # an '*' sign
+                        self._draw_asterisk(draw, ptsize, (x, y),
+                                            outline=outline, width=width,
+                                            outline_opacity=outline_opacity)
+                    else:
+                        raise ValueError("Unsupported symbol type: " + str(symbol))
+
+                elif desc is None:
+                    logger.error("'ptsize' is 0 and 'desc' is None, nothing will be added to the image.")
+
+                if desc is not None:
+                    text_position = [x + ptsize, y]  # draw the text box next to the point
+                    font = self._get_font(outline, font_file, font_size)
+
+                    new_kwargs = kwargs.copy()
+
+                    box_outline = new_kwargs.pop('box_outline', 'white')
+                    box_opacity = new_kwargs.pop('box_opacity', 0)
+
+                    # add text_box
+                    self._draw_text_box(draw, text_position, desc, font, outline,
+                                        box_outline, box_opacity, **new_kwargs)
+
+            logger.debug("Point %s has been added to the image", str((lon, lat)))
+
+        self._finalize(draw)
+
 
 def _get_lon_lat_bounding_box(area_extent, x_size, y_size, prj):
     """Get extreme lon and lat values


=====================================
pycoast/cw_pil.py
=====================================
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 # pycoast, Writing of coastlines, borders, and rivers to images in Python
 #
-# Copyright (C) 2011-2018 PyCoast Developers
+# Copyright (C) 2011-2020 PyCoast Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -61,11 +61,11 @@ class ContourWriterPIL(ContourWriterBase):
         draw.rectangle(coordinates, fill=kwargs['fill'], outline=kwargs['outline'])
 
     def _draw_text_box(self, draw, text_position, text, font, outline,
-                       box_outline, box_opacity):
+                       box_outline, box_opacity, **kwargs):
         """Add text box in xy."""
         if box_outline is not None:
             logger.warning(
-                "Box background will not added; please install aggdraw lib")
+                "Box background will not be added; please use ContourWriterAGG module")
 
         self._draw_text(
             draw, text_position, text, font, align="no", fill=outline)
@@ -74,6 +74,33 @@ class ContourWriterPIL(ContourWriterBase):
         """Draw line."""
         draw.line(coordinates, fill=kwargs['outline'])
 
+    def _draw_asterisk(self, draw, pt_size, coordinate, **kwargs):
+        """Draw a asterisk sign '*' center at the given coordinate """
+        half_ptsize = int(round(pt_size / 2.))
+        x, y = coordinate
+
+        outline = kwargs.get('outline', 'white')
+
+        # draw '|'
+        (x_bm, y_bm) = (x, y - half_ptsize)  # bottom middle point
+        (x_tm, y_tm) = (x, y + half_ptsize)  # top middle point
+        self._draw_line(draw, [(x_bm, y_bm), (x_tm, y_tm)], outline=outline)
+
+        # draw '-'
+        (x_lm, y_lm) = (x - half_ptsize, y)  # left middle point
+        (x_rm, y_rm) = (x + half_ptsize, y)  # right middle point
+        self._draw_line(draw, [(x_lm, y_lm), (x_rm, y_rm)], outline=outline)
+
+        # draw '/'
+        (x_bl, y_bl) = (x - half_ptsize, y - half_ptsize)  # bottom left point
+        (x_tr, y_tr) = (x + half_ptsize, y + half_ptsize)  # top right point
+        self._draw_line(draw, [(x_bl, y_bl), (x_tr, y_tr)], outline=outline)
+
+        # draw '\'
+        (x_tl, y_tl) = (x - half_ptsize, y + half_ptsize)  # top left point
+        (x_br, y_br) = (x + half_ptsize, y - half_ptsize)  # bottom right point
+        self._draw_line(draw, [(x_tl, y_tl), (x_br, y_br)], outline=outline)
+
     def add_shapefile_shapes(self, image, area_def, filename, feature_type=None,
                              fill=None, outline='white',
                              x_offset=0, y_offset=0):


=====================================
pycoast/tests/coasts_and_grid_agg.ini
=====================================
@@ -12,7 +12,7 @@ lat_minor = 2.0
 minor_outline = blue
 outline = blue
 lon_placement = tblr
-lat_placement = 
-font = pycoast/tests/test_data/DejaVuSerif.ttf
+lat_placement = ''
+font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
 font_size = 10
 


=====================================
pycoast/tests/grid_nh_cfg_agg.png
=====================================
Binary files /dev/null and b/pycoast/tests/grid_nh_cfg_agg.png differ


=====================================
pycoast/tests/nh_points_agg.ini
=====================================
@@ -0,0 +1,25 @@
+[coasts]
+level = 4
+resolution = l
+outline = black
+
+[borders]
+level = 1
+outline = black
+width = 3
+resolution = c
+
+[points]
+points_list = ((2.3522, 48.8566), 'Paris'), ((0.1278, 51.5074), 'London')
+font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
+symbol = circle
+ptsize = 16
+outline = black
+width = 3
+fill = red
+fill_opacity = 128
+box_outline = blue
+box_linewidth = 0.5
+box_fill = yellow
+box_opacity = 200
+


=====================================
pycoast/tests/nh_points_agg.png
=====================================
Binary files /dev/null and b/pycoast/tests/nh_points_agg.png differ


=====================================
pycoast/tests/nh_points_cfg_pil.png
=====================================
Binary files /dev/null and b/pycoast/tests/nh_points_cfg_pil.png differ


=====================================
pycoast/tests/nh_points_pil.ini
=====================================
@@ -0,0 +1,19 @@
+[coasts]
+level = 4
+resolution = l
+outline = black
+
+[borders]
+level = 1
+outline = black
+width = 3
+resolution = c
+
+[points]
+points_list = ((13.4050, 52.5200), 'Berlin'), ((12.4964, 41.9028), 'Rome')
+font = 'pycoast/tests/test_data/DejaVuSerif.ttf'
+symbol = square
+ptsize = 6
+outline = red
+fill = yellow
+box_outline = black


=====================================
pycoast/tests/nh_points_pil.png
=====================================
Binary files /dev/null and b/pycoast/tests/nh_points_pil.png differ


=====================================
pycoast/tests/test_pycoast.py
=====================================
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 # pycoast, Writing of coastlines, borders and rivers to images in Python
 #
-# Copyright (C) 2011-2018 PyCoast Developers
+# Copyright (C) 2011-2020 PyCoast Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ import numpy as np
 from PIL import Image, ImageFont
 import time
 
+
 def tmp(f):
     f.tmp = True
     return f
@@ -357,6 +358,45 @@ class TestPIL(TestPycoast):
         self.assertTrue(fft_metric(grid_data, res),
                         'Writing of nh polygons failed')
 
+    def test_add_points_pil(self):
+        from pycoast import ContourWriterPIL
+        from pyresample.geometry import AreaDefinition
+
+        font_file = os.path.join(os.path.dirname(__file__), 'test_data',
+                                 'DejaVuSerif.ttf')
+        grid_img = Image.open(os.path.join(os.path.dirname(__file__),
+                                           'nh_points_pil.png'))
+        grid_data = np.array(grid_img)
+
+        img = Image.new('RGB', (1024, 1024), (255, 255, 255))
+
+        proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
+        area_extent = (-5326849.0625, -5326849.0625,
+                       5326849.0625, 5326849.0625)
+
+        area_def = AreaDefinition('nh', 'nh', 'nh', proj4_string,
+                                  1024, 1024, area_extent)
+
+        cw = ContourWriterPIL(gshhs_root_dir)
+        cw.add_coastlines(img, area_def, outline='black', resolution='l',
+                          level=4)
+        cw.add_borders(img, area_def, outline='black', level=1,
+                       resolution='c')
+
+        points_list = [((13.4050, 52.5200), 'Berlin')]
+        cw.add_points(img, area_def, points_list=points_list, font_file=font_file,
+                      symbol='asterisk', ptsize=6, outline='red',
+                      box_outline='black')
+
+        points_list = [((12.4964, 41.9028), 'Rome')]
+        cw.add_points(img, area_def, points_list=points_list, font_file=font_file,
+                      symbol='square', ptsize=6, outline='blue', fill='yellow',
+                      box_outline='black')
+
+        res = np.array(img)
+        self.assertTrue(fft_metric(grid_data, res),
+                        'Writing of nh points failed')
+
     def test_add_shapefile_shapes(self):
         from pycoast import ContourWriterPIL
         grid_img = Image.open(os.path.join(os.path.dirname(__file__),
@@ -412,6 +452,34 @@ class TestPIL(TestPycoast):
         self.assertTrue(fft_metric(grid_data, res),
                         'Writing of nh grid failed')
 
+    def test_config_file_points_and_borders_pil(self):
+        from pycoast import ContourWriterPIL
+        from pyresample.geometry import AreaDefinition
+
+        config_file = os.path.join(os.path.dirname(__file__),
+                                   'nh_points_pil.ini')
+
+        grid_img = Image.open(os.path.join(os.path.dirname(__file__),
+                                           'nh_points_cfg_pil.png'))
+        grid_data = np.array(grid_img)
+
+        img = Image.new('RGB', (1024, 1024), (255, 255, 255))
+
+        proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
+        area_extent = (-5326849.0625, -5326849.0625,
+                       5326849.0625, 5326849.0625)
+
+        area_def = AreaDefinition('nh', 'nh', 'nh', proj4_string,
+                                  1024, 1024, area_extent)
+
+        cw = ContourWriterPIL(gshhs_root_dir)
+
+        cw.add_overlay_from_config(config_file, area_def, img)
+
+        res = np.array(img)
+        self.assertTrue(fft_metric(grid_data, res),
+                        'Writing of nh points failed')
+
 
 class TestPILAGG(TestPycoast):
 
@@ -517,9 +585,9 @@ class TestPILAGG(TestPycoast):
         cw = ContourWriterAGG(gshhs_root_dir)
 
         cw.add_coastlines(img, area_def, resolution='l', level=4)
-        font = aggdraw.Font('blue', os.path.join(os.path.dirname(__file__),
-                                                 'test_data',
-                                                 'DejaVuSerif.ttf'), size=16,
+        font = aggdraw.Font('blue',
+                            os.path.join(os.path.dirname(__file__), 'test_data', 'DejaVuSerif.ttf'),
+                            size=16,
                             opacity=200)
         cw.add_grid(img, area_def, (10.0, 10.0), (2.0, 2.0), font=font,
                     outline='blue', outline_opacity=255, width=1.0,
@@ -644,6 +712,43 @@ class TestPILAGG(TestPycoast):
         self.assertTrue(fft_metric(grid_data, res),
                         'Writing of nh polygons failed')
 
+    def test_add_points_agg(self):
+        from pycoast import ContourWriterAGG
+        from pyresample.geometry import AreaDefinition
+
+        font_file = os.path.join(os.path.dirname(__file__), 'test_data',
+                                 'DejaVuSerif.ttf')
+
+        grid_img = Image.open(os.path.join(os.path.dirname(__file__),
+                                           'nh_points_agg.png'))
+        grid_data = np.array(grid_img)
+
+        img = Image.new('RGB', (1024, 1024), (255, 255, 255))
+        proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
+        area_extent = (-5326849.0625, -5326849.0625,
+                       5326849.0625, 5326849.0625)
+
+        area_def = AreaDefinition('nh', 'nh', 'nh', proj4_string,
+                                  1024, 1024, area_extent)
+
+        cw = ContourWriterAGG(gshhs_root_dir)
+        cw.add_coastlines(img, area_def, outline='black', resolution='l',
+                          level=4)
+        cw.add_borders(img, area_def, outline='black', width=3, level=1,
+                       resolution='c')
+
+        points_list = [((2.3522, 48.8566), 'Paris'),
+                       ((0.1278, 51.5074), 'London')]
+        cw.add_points(img, area_def, points_list=points_list, font_file=font_file,
+                      symbol='circle', ptsize=16,
+                      outline='black', width=3,
+                      fill='red', fill_opacity=128,
+                      box_outline='blue', box_linewidth=0.5,
+                      box_fill='yellow', box_opacity=200)
+
+        res = np.array(img)
+        self.assertTrue(fft_metric(grid_data, res), 'Writing of nh points failed')
+
     def test_add_shapefile_shapes_agg(self):
         from pycoast import ContourWriterAGG
         grid_img = Image.open(os.path.join(os.path.dirname(__file__),
@@ -677,31 +782,59 @@ class TestPILAGG(TestPycoast):
         self.assertTrue(
             fft_metric(grid_data, res), 'Writing of Brazil shapefiles failed')
 
-    @unittest.skip("All kwargs are not supported, so can't create equal results")
+#    @unittest.skip("All kwargs are not supported, so can't create equal results")
     def test_config_file_coasts_and_grid(self):
         from pycoast import ContourWriterAGG
         from pyresample.geometry import AreaDefinition
         overlay_config = os.path.join(os.path.dirname(__file__),
                                       "coasts_and_grid_agg.ini")
         grid_img = Image.open(os.path.join(os.path.dirname(__file__),
-                                           'grid_nh_agg.png'))
+                                           'grid_nh_cfg_agg.png'))
         grid_data = np.array(grid_img)
         proj_dict = {'proj': 'laea', 'lat_0': 90.0, 'lon_0': 0.0,
                      'a': 6371228.0, 'units': 'm'}
         area_extent = (-5326849.0625, -5326849.0625,
                        5326849.0625, 5326849.0625)
-        area_def = AreaDefinition('nh', 'nh', 'nh', proj_dict, 425, 425,
+        area_def = AreaDefinition('nh', 'nh', 'nh', proj_dict, 850, 850,
                                   area_extent)
 
         cw = ContourWriterAGG(gshhs_root_dir)
         overlay = cw.add_overlay_from_config(overlay_config, area_def)
-        img = Image.new('RGB', (425, 425))
+        img = Image.new('RGB', (850, 850), (255, 255, 255))
         img.paste(overlay, mask=overlay)
 
         res = np.array(img)
         self.assertTrue(fft_metric(grid_data, res),
                         'Writing of nh grid failed')
 
+    def test_config_file_points_and_borders_agg(self):
+        from pycoast import ContourWriterAGG
+        from pyresample.geometry import AreaDefinition
+
+        config_file = os.path.join(os.path.dirname(__file__),
+                                   'nh_points_agg.ini')
+
+        grid_img = Image.open(os.path.join(os.path.dirname(__file__),
+                                           'nh_points_agg.png'))
+        grid_data = np.array(grid_img)
+
+        img = Image.new('RGB', (1024, 1024), (255, 255, 255))
+
+        proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
+        area_extent = (-5326849.0625, -5326849.0625,
+                       5326849.0625, 5326849.0625)
+
+        area_def = AreaDefinition('nh', 'nh', 'nh', proj4_string,
+                                  1024, 1024, area_extent)
+
+        cw = ContourWriterAGG(gshhs_root_dir)
+
+        cw.add_overlay_from_config(config_file, area_def, img)
+
+        res = np.array(img)
+        self.assertTrue(fft_metric(grid_data, res),
+                        'Add points with agg module from a config file failed')
+
     def test_coastlines_convert_to_rgba_agg(self):
         from pycoast import ContourWriterAGG
         proj4_string = \
@@ -784,30 +917,37 @@ class TestFromConfig(TestPycoast):
                     'borders': {'outline': (255, 0, 0), 'resolution': 'c'},
                     'rivers': {'outline': 'blue', 'resolution': 'c', 'level': 5}}
 
+        # Create the original cache file
         cache_filename = os.path.join(tmp, 'pycoast_cache_fakearea.png')
         img = cw.add_overlay_from_dict(overlays, area_def)
         res = np.array(img)
         self.assertTrue(fft_metric(euro_data, res),
                         'Writing of contours failed')
         self.assertTrue(os.path.isfile(cache_filename))
+        mtime = os.path.getmtime(cache_filename)
 
-        current_time = time.time()
-
-        img = cw.add_overlay_from_dict(overlays, area_def, current_time)
+        # Reuse the generated cache file
+        img = cw.add_overlay_from_dict(overlays, area_def)
+        res = np.array(img)
+        self.assertTrue(fft_metric(euro_data, res),
+                        'Writing of contours failed')
+        self.assertTrue(os.path.isfile(cache_filename))
+        self.assertEqual(os.path.getmtime(cache_filename), mtime)
 
+        # Regenerate cache file
+        current_time = time.time()
+        cw.add_overlay_from_dict(overlays, area_def, current_time)
         mtime = os.path.getmtime(cache_filename)
-
         self.assertGreater(mtime, current_time)
         self.assertTrue(fft_metric(euro_data, res),
                         'Writing of contours failed')
 
-        img = cw.add_overlay_from_dict(overlays, area_def, current_time)
-
+        cw.add_overlay_from_dict(overlays, area_def, current_time)
         self.assertEqual(os.path.getmtime(cache_filename), mtime)
         self.assertTrue(fft_metric(euro_data, res),
                         'Writing of contours failed')
         overlays['cache']['regenerate'] = True
-        img = cw.add_overlay_from_dict(overlays, area_def)
+        cw.add_overlay_from_dict(overlays, area_def)
 
         self.assertNotEqual(os.path.getmtime(cache_filename), mtime)
         self.assertTrue(fft_metric(euro_data, res),


=====================================
pycoast/version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
     # setup.py/versioneer.py will grep for the variable names, so they must
     # each be defined on a line of their own. _version.py will just call
     # get_keywords().
-    git_refnames = " (HEAD -> master, tag: v1.3.2)"
-    git_full = "eb35e257be4bc570dc6f08fb044aca259fe71890"
-    git_date = "2019-12-06 14:39:30 -0600"
+    git_refnames = " (tag: v1.4.0)"
+    git_full = "c96f05892af2bdc7148dbfdea7930385ca39aa1e"
+    git_date = "2020-06-08 14:34:54 -0500"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/pycoast/-/commit/2ec45a3907b99bbf5027435e3cda6e2d4045080a

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pycoast/-/commit/2ec45a3907b99bbf5027435e3cda6e2d4045080a
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20200611/bbb17c17/attachment-0001.html>


More information about the Pkg-grass-devel mailing list