Source code for swmmio.graphics.swmm_graphics

# graphical functions for SWMM files
import os
import tempfile

from PIL import Image, ImageDraw

from swmmio.defs.config import BETTER_BASEMAP_PATH
from swmmio.graphics import config
from swmmio.defs.constants import white
from swmmio.graphics.utils import px_to_irl_coords, save_image
from swmmio.utils import spatial
from swmmio.utils.spatial import centroid_and_bbox_from_coords
from swmmio.graphics.drawing import (annotate_streets, annotate_title, annotate_details, annotate_timestamp,
                                     draw_conduit, draw_node)


def _draw_basemap(draw, img, bbox, px_width, shift_ratio):
    """
    given the shapefiles in config.basemap_options, render each layer
    on the model basemap.
    """

    for f in config.basemap_options['features']:

        shp_path = os.path.join(config.basemap_shapefile_dir, f['feature'])
        df = spatial.read_shapefile(shp_path)[f['cols'] + ['coords']]
        df = px_to_irl_coords(df, bbox=bbox, shift_ratio=shift_ratio,
                              px_width=px_width)[0]

        if 'ST_NAME' in df.columns:
            # this is a street, draw a polyline accordingly
            df.apply(lambda r: draw.line(r.draw_coords, fill=f['fill']), axis=1)
            annotate_streets(df, img, 'ST_NAME')
        else:
            df.apply(lambda r: draw.polygon(r.draw_coords,
                                            fill=f['fill']), axis=1)


[docs] def draw_model(model=None, nodes=None, conduits=None, parcels=None, title=None, annotation=None, file_path=None, bbox=None, px_width=2048.0): """ Create a PNG rendering of the model and model results. Notes ----- A swmmio.Model object can be passed in independently, or Pandas Dataframes for the nodes and conduits of a model may be passed in. A dataframe containing parcel data can optionally be passed in. Parameters ---------- model : swmmio.Model, optional A swmmio.Model object. nodes : pandas.DataFrame, optional DataFrame for the nodes of a model. Required if model is not provided. conduits : pandas.DataFrame, optional DataFrame for the conduits of a model. Required if model is not provided. parcels : pandas.DataFrame, optional DataFrame containing parcel data. title : str, optional String to be written in the top left of the PNG. annotation : str, optional String to be written in the bottom left of the PNG. file_path : str, optional File path where PNG should be saved. If not specified, a PIL Image object is returned. bbox : tuple of tuple of float, optional Coordinates representing the bottom left and top right corner of a bounding box. The rendering will be clipped to this box. If not provided, the rendering will clip tightly to the model extents. Example: ((2691647, 221073), (2702592, 227171)). px_width : float, optional Width of the image in pixels. Default is 2048.0. Returns ------- PIL.Image.Image The rendered image. """ # gather the nodes and conduits data if a swmmio Model object was passed in if model is not None: nodes = model.nodes() conduits = model.links() # antialias X2 xplier = 1 xplier *= px_width / 1024 # scale the symbology sizes px_width = px_width * 2 # compute draw coordinates, and the image dimensions (in px) conduits, bb, h, w, shift_ratio = px_to_irl_coords(conduits, bbox=bbox, px_width=px_width) nodes = px_to_irl_coords(nodes, bbox=bb, px_width=px_width)[0] # create the PIL image and draw objects img = Image.new('RGB', (w, h), white) draw = ImageDraw.Draw(img) # draw the basemap if required if config.include_basemap is True: _draw_basemap(draw, img, bb, px_width, shift_ratio) if parcels is not None: # expects dataframe with coords and draw color column par_px = px_to_irl_coords(parcels, bbox=bb, shift_ratio=shift_ratio, px_width=px_width)[0] par_px.apply(lambda r: draw.polygon(r.draw_coords, fill=r.draw_color), axis=1) # start the draw fest, mapping draw methods to each row in the dataframes conduits.apply(lambda row: draw_conduit(row, draw), axis=1) nodes.apply(lambda row: draw_node(row, draw), axis=1) # ADD ANNOTATION AS NECESSARY if title: annotate_title(title, draw) if annotation: annotate_details(annotation, draw) annotate_timestamp(draw) # SAVE IMAGE TO DISK if file_path: save_image(img, file_path) return img
[docs] def create_map(model=None, filename=None, basemap=None, auto_open=False): """ Export model as a geojson object and create an HTML map. Parameters ---------- model : object, optional The model object to be exported. Must have a valid CRS (Coordinate Reference System). filename : str, optional The filename for the output HTML file. If None, a temporary file will be created. basemap : str, optional The path to the basemap file. If None, a default basemap path will be used. auto_open : bool, optional If True, the generated HTML file will be automatically opened in a web browser. Returns ------- str The content of the generated HTML file if `filename` is None, otherwise returns an empty string. Raises ------ ValueError If the model object does not have a valid CRS. Notes ----- The function reads a basemap file and inserts geojson data of the model's links and nodes into it. It also sets the map's center and bounding box based on the model's coordinates. """ import geojson basemap = BETTER_BASEMAP_PATH if basemap is None else basemap return_html = False if filename is not None else True if filename is None: filename = os.path.join(tempfile.gettempdir(), f'{model.name}.html') if model.crs: model.to_crs("EPSG:4326") else: raise ValueError('Model object must have a valid crs') # get map centroid and bbox c, bbox = centroid_and_bbox_from_coords(model.inp.coordinates) # start writing that thing with open(basemap, 'r') as bm: with open(filename, 'w') as newmap: for line in bm: if 'INSERT GEOJSON HERE' in line: newmap.write(f'conduits = {geojson.dumps(model.links.geojson)}\n') newmap.write(f'nodes = {geojson.dumps(model.nodes.geojson)}\n') elif '// INSERT MAP CENTER HERE' in line: newmap.write('\tcenter:[{}, {}],\n'.format(c[0], c[1])) elif '// INSERT BBOX HERE' in line and bbox is not None: newmap.write(f'\tmap.fitBounds([[{bbox[0]}, {bbox[1]}], [{bbox[2]}, {bbox[3]}]]);\n') else: newmap.write(line) if return_html: with open(filename, 'r') as f: return f.read()