def __init__(self, path: Path): self.path = path self.svg_origin = CanvasCoordinate.origin() self.position = CanvasCoordinate.origin() self.width = None self.height = None self.rotation = 0
def test_identity(self): assert Cc.from_pt(1, 1).pt == (1, 1) assert Cc.from_in(1, 1).inches == (1, 1) assert Cc.from_cm(1, 1).cm == (1, 1) assert Cc.from_mm(1, 1).mm == (1, 1) assert Cc.from_px(1, 1).px == (1, 1) assert Cc.from_pt(2, 2).pt == (2, 2) assert Cc.from_in(2, 2).inches == (2, 2) assert Cc.from_cm(2, 2).cm == (2, 2) assert Cc.from_mm(2, 2).mm == (2, 2) assert Cc.from_px(2, 2).px == (2, 2)
def test_output_translate_rotate_scale(self): path = Path(__file__).parent.joinpath('output/svg_trs.svg') path.unlink(missing_ok=True) canvas_builder = CanvasBuilder() canvas_builder.set_path(path) canvas_builder.set_size( Cu.from_cm(4), Cu.from_cm(4) ) canvas = canvas_builder.build() background = Background() background.color = (0.8, 1, 0.8, 1) background.draw(canvas) svg = Svg(Path(__file__).parent.joinpath('test_svg_grid.svg')) svg_size = svg.read_svg_size() # Should set the origin of the image to the center. svg.svg_origin = Cc(svg_size[0] / 2, svg_size[1] / 2) # Should position the image in the center of the screen. svg.position = Cc.from_cm(2, 2) # Should rotate the image clock-wise. svg.rotation = math.pi / 8 # Resizes the image to almost the size of the canvas, but not exactly. svg.width = Cu.from_cm(3) svg.height = Cu.from_cm(3) svg.draw(canvas) canvas.close() assert path.exists()
def test_multi_polygons(self): path = Path(__file__).parent.joinpath( 'output/stripe_filled_polygon_drawer_multi_polygons.svg') path.unlink(missing_ok=True) canvas_builder = CanvasBuilder() canvas_builder.set_path(path) canvas_builder.set_size(Cu.from_pt(100), Cu.from_pt(100)) canvas = canvas_builder.build() drawer = StripeFilledPolygonDrawer() drawer.stripe_colors = [(1, 1, 0), None, (0.7, 0, 0.7), (0, 0, 0)] drawer.stripe_widths = [ Cu.from_pt(5), Cu.from_pt(5), Cu.from_pt(5), Cu.from_pt(5), ] drawer.stripe_origin = CanvasCoordinate.from_pt(30, 30) drawer.stripe_angle = math.pi / 8 # Vertical stripes drawer.geoms = [ MultiPolygon([ Polygon([ (30, 30), (70, 30), (70, 70), (30, 70), (30, 30), ], [[ (35, 35), (65, 35), (65, 65), (35, 65), (35, 35), ]]), Polygon([ (40, 40), (60, 40), (60, 60), (40, 60), (40, 40), ]) ]) ] drawer.draw(canvas) canvas.close() assert path.exists() with open(path, 'r') as file: data = file.read() assert data.find('fill:rgb(100%,100%,0%)') != -1 assert data.find('fill:rgb(0%,0%,0%)') != -1 assert data.find('fill:rgb(70%,0%,70%)') != -1 assert data.find('stroke:none') != -1 assert data.find('stroke-width:') == -1
def logical_extents(self) -> CanvasBbox: extent = self._layout.get_extents()[1] x = CanvasUnit.from_pt(pangocffi.units_to_double(extent.x)) y = CanvasUnit.from_pt(pangocffi.units_to_double(extent.y)) x += self._position.x y += self._position.y width = CanvasUnit.from_pt(pangocffi.units_to_double(extent.width)) height = CanvasUnit.from_pt(pangocffi.units_to_double(extent.height)) return CanvasBbox(CanvasCoordinate(x, y), width, height)
def test_scale(self): path = Path(__file__).parent.joinpath('output/svg_scale.svg') path.unlink(missing_ok=True) canvas_builder = CanvasBuilder() canvas_builder.set_path(path) canvas_builder.set_size( Cu.from_cm(9), Cu.from_cm(6) ) canvas = canvas_builder.build() background = Background() background.color = (1, 0.8, 0.8, 1) background.draw(canvas) # No scaling svg = Svg(Path(__file__).parent.joinpath('test_svg.svg')) svg.position = Cc.from_cm(1, 1) svg.draw(canvas) # Scale 2x by height svg = Svg(Path(__file__).parent.joinpath('test_svg.svg')) svg.position = Cc.from_cm(5, 1) svg.width = Cu.from_cm(3) svg.draw(canvas) # Scale 2x by height svg = Svg(Path(__file__).parent.joinpath('test_svg.svg')) svg.position = Cc.from_cm(1, 4) svg.height = Cu.from_cm(1) svg.draw(canvas) # Scale without ratio preservation svg = Svg(Path(__file__).parent.joinpath('test_svg.svg')) svg.position = Cc.from_cm(5, 4) svg.width = Cu.from_cm(3) svg.height = Cu.from_cm(1) svg.draw(canvas) canvas.close() assert path.exists()
def test_conversion(self): wgs84_crs = pyproj.CRS.from_epsg(4326) british_crs = pyproj.CRS.from_epsg(27700) coordinate_to_project = GeoCoordinate(55.862777, -4.260919, wgs84_crs) expected_canvas_coordinates = CanvasCoordinate( CanvasUnit.from_cm(1 + 6), CanvasUnit.from_cm(1 + 4)).pt origin_for_geo = GeoCoordinate(258000, 666000, british_crs) origin_for_canvas = CanvasCoordinate(CanvasUnit.from_cm(1), CanvasUnit.from_cm(1)) # 100 meters for every centimeter geo_to_canvas_scale = geo_canvas_ops.GeoCanvasScale( 100, CanvasUnit.from_cm(1)) transformation_func = geo_canvas_ops.build_transformer( crs=british_crs, data_crs=wgs84_crs, scale=geo_to_canvas_scale, origin_for_geo=origin_for_geo, origin_for_canvas=origin_for_canvas) self.assert_coordinates_are_close( transformation_func(*coordinate_to_project.tuple), expected_canvas_coordinates) # Repeat the same test, but this time without data_crs: transformation_func = geo_canvas_ops.build_transformer( crs=british_crs, scale=geo_to_canvas_scale, origin_for_geo=origin_for_geo, origin_for_canvas=origin_for_canvas) coordinate_to_project = GeoCoordinate(258600, 665600, british_crs) self.assert_coordinates_are_close( transformation_func(*coordinate_to_project.tuple), expected_canvas_coordinates)
def build_transformer( # The Coordinate Reference System for plotting coordinate data onto # the canvas. crs: pyproj.CRS, # Controls the scale that things on the map will appear in. For # example, the number of meters per centimeter. scale: GeoCanvasScale, # Controls where the map should point to. If `origin_for_canvas` is the # default, this geographic coordinate appear on the top-left corner of # the canvas. origin_for_geo: GeoCoordinate, # Controls where on the canvas the origin should correspond to (Useful # if your map has margins on the side of the map). Defaults to the # top-left corner of the canvas. origin_for_canvas: CanvasCoordinate = CanvasCoordinate.origin(), # The Coordinate Reference System for the input data. For example, if # your data is longitude/latitude data, you will want to set the # `data_crs` as `pyproj.CRS.from_epsg(4326)`. data_crs: Optional[pyproj.CRS] = None, ) -> Callable[[float, float], Tuple[float, float]]: data_transformer: Optional[pyproj.Transformer] = None if data_crs is not None: data_transformer = pyproj.Transformer.from_proj(data_crs, crs) transformed_origin_for_geo = pyproj.Transformer\ .from_proj(origin_for_geo.crs, crs)\ .transform(*origin_for_geo.tuple) scale_factor = scale.geo_units / scale.canvas_units.pt def projection(x: float, y: float) -> Tuple[float, float]: if data_transformer is not None: coord = data_transformer.transform(x, y) else: coord = (x, y) translated = (coord[0] - transformed_origin_for_geo[0], coord[1] - transformed_origin_for_geo[1]) # The y-coordinate is inverted because the coordinate space in computer # graphics is inverted. return (translated[0] / scale_factor + origin_for_canvas.x.pt, translated[1] / -scale_factor + origin_for_canvas.y.pt) return projection
def test_setters_and_getters(self): # Create a canvas for the sake of instantiating a layout object. We do # this because that is how Pango actually resolves the font dimensions. path = Path(__file__).parent.joinpath( 'output/layout_setters_and_getters.svg') canvas_builder = CanvasBuilder() canvas_builder.set_path(path) canvas_builder.set_size(Cu.from_pt(100), Cu.from_pt(100)) canvas = canvas_builder.build() layout = Layout(canvas) layout.set_text('Hello world') layout.set_markup('<span weight="bold">Hello world!</span>') assert layout.width is None assert layout.height is None layout.width = Cu.from_pt(100) layout.height = Cu.from_pt(50) assert layout.width.pt == 100 assert layout.height.pt == 50 layout.reset_width() layout.reset_height() assert layout.width is None assert layout.height is None assert layout.position.x.pt == 0 assert layout.position.y.pt == 0 layout.position = CanvasCoordinate(Cu.from_pt(10), Cu.from_pt(5)) assert layout.position.x.pt == 10 assert layout.position.y.pt == 5 assert layout.color == (0, 0, 0, 1) layout.color = (0, 1, 0, 0.5) assert layout.color == (0, 1, 0, 0.5) assert layout.alignment == Alignment.LEFT layout.alignment = Alignment.RIGHT assert layout.alignment == Alignment.RIGHT extents = layout.logical_extents assert extents.pos.x.pt == 10 assert extents.pos.y.pt == 5 assert extents.width.pt > 1 assert extents.height.pt > 1
def test_two_vertical_stripes(self): path = Path(__file__).parent.joinpath( 'output/stripe_filled_polygon_drawer_two_vertical_stripes.svg') path.unlink(missing_ok=True) canvas_builder = CanvasBuilder() canvas_builder.set_path(path) canvas_builder.set_size(Cu.from_pt(100), Cu.from_pt(100)) canvas = canvas_builder.build() drawer = StripeFilledPolygonDrawer() drawer.stripe_colors = [(0, 1, 0), (0, 0, 1)] drawer.stripe_widths = [Cu.from_pt(20), Cu.from_pt(20)] drawer.stripe_origin = CanvasCoordinate.from_pt(30, 30) drawer.stripe_angle = math.pi / 2 # Vertical stripes drawer.geoms = [ Polygon([ (30, 30), (70, 30), (70, 70), (30, 70), (30, 30), ]) ] drawer.draw(canvas) canvas.close() assert path.exists() with open(path, 'r') as file: data = file.read() assert data.find('M 30 30 L 30 70 L 50 70 L 50 30 Z M 30 30') != -1 assert data.find('M 50 30 L 50 70 L 70 70 L 70 30 Z M 50 30') != -1 assert data.find('fill:rgb(0%,100%,0%)') != -1 assert data.find('fill:rgb(0%,0%,100%)') != -1 assert data.find('stroke:none') != -1 assert data.find('stroke-width:') == -1
def __init__(self): self.geoms = [] self.stripe_widths = [CanvasUnit.from_pt(1)] self.stripe_colors = [(0, 0, 0, 1)] self.stripe_angle = 0 self.stripe_origin = CanvasCoordinate.origin()
def draw_wgs84( self, canvas: Canvas, mask_wgs84: MultiPolygon, world_map_wgs84: MultiPolygon, flight_paths_wgs84: MultiLineString ): wgs84_crs = CRS.from_epsg(4326) origin_for_geo = GeoCoordinate( 0, 0, wgs84_crs ) origin_for_canvas = CanvasCoordinate( Cu.from_px(self.margin * 2 + self.azimuthal_width + self.wgs84_width / 2), Cu.from_px(self.margin + self.wgs84_height / 2) ) # 1 pixel for every degree geo_to_canvas_scale = geo_canvas_ops.GeoCanvasScale( 1, Cu.from_px(1) ) # Todo: These transformations should be handled by build_transformer. mask_wgs84 = ops.transform(lambda x, y: (y, x), mask_wgs84) world_map_wgs84 = ops.transform(lambda x, y: (y, x), world_map_wgs84) flight_paths_wgs84 = ops.transform( lambda x, y: (y, x), flight_paths_wgs84 ) # Generate a polygon that represents the full range. domain_wgs84 = Polygon( [(-180, -90), (180, -90), (180, 90), (-180, 90)] ) wgs84_to_canvas = geo_canvas_ops.build_transformer( crs=wgs84_crs, data_crs=wgs84_crs, scale=geo_to_canvas_scale, origin_for_geo=origin_for_geo, origin_for_canvas=origin_for_canvas ) mask_canvas = ops.transform(wgs84_to_canvas, mask_wgs84) world_map_canvas = ops.transform(wgs84_to_canvas, world_map_wgs84) flight_paths_canvas = ops.transform( wgs84_to_canvas, flight_paths_wgs84 ) domain_canvas = ops.transform(wgs84_to_canvas, domain_wgs84) polygon_drawer = PolygonDrawer() polygon_drawer.fill_color = (0, 0, 0) polygon_drawer.geoms = [domain_canvas] polygon_drawer.draw(canvas) polygon_drawer = PolygonDrawer() polygon_drawer.fill_color = self.sea_color polygon_drawer.geoms = [mask_canvas] polygon_drawer.draw(canvas) polygon_drawer = PolygonDrawer() polygon_drawer.fill_color = self.land_color polygon_drawer.geoms = [world_map_canvas] polygon_drawer.draw(canvas) polygon_drawer = LineDrawer() polygon_drawer.stroke_color = self.line_color polygon_drawer.geoms = [flight_paths_canvas] polygon_drawer.draw(canvas)
def draw_azimuthal( self, canvas: Canvas, crs: CRS, mask_proj: Polygon, world_map_wgs84: MultiPolygon, flight_paths_wgs84: MultiLineString ): origin_for_geo = GeoCoordinate(0, 0, crs) origin_x = Cu.from_px(self.margin + self.azimuthal_width / 2) origin_y = Cu.from_px(self.margin + self.azimuthal_height / 2) origin_for_canvas = CanvasCoordinate(origin_x, origin_y) # Fit the projected radius as the width of the canvas geo_to_canvas_scale = geo_canvas_ops.GeoCanvasScale( crs.ellipsoid.semi_major_metre, Cu.from_px(self.azimuthal_width / 2) ) # Convert the projected mask to the canvas. proj_to_canvas = geo_canvas_ops.build_transformer( crs=crs, data_crs=crs, scale=geo_to_canvas_scale, origin_for_geo=origin_for_geo, origin_for_canvas=origin_for_canvas ) mask_canvas = ops.transform(proj_to_canvas, mask_proj) # Convert the world map, from wgs84 to proj to canvas wgs84_crs = CRS.from_epsg(4326) wgs84_to_canvas = geo_canvas_ops.build_transformer( crs=crs, data_crs=wgs84_crs, scale=geo_to_canvas_scale, origin_for_geo=origin_for_geo, origin_for_canvas=origin_for_canvas ) world_map_canvas = transform_interpolated_euclidean( wgs84_to_canvas, world_map_wgs84 ) flight_paths_canvas = transform_interpolated_euclidean( wgs84_to_canvas, flight_paths_wgs84 ) # Render the polygons polygon_drawer = PolygonDrawer() polygon_drawer.fill_color = self.sea_color polygon_drawer.geoms = [mask_canvas] polygon_drawer.draw(canvas) polygon_drawer = PolygonDrawer() polygon_drawer.fill_color = self.land_color polygon_drawer.geoms = [world_map_canvas] polygon_drawer.draw(canvas) polygon_drawer = LineDrawer() polygon_drawer.stroke_color = self.line_color polygon_drawer.geoms = [flight_paths_canvas] polygon_drawer.draw(canvas)
def __init__(self, canvas: Canvas): self._layout = pangocairocffi.create_layout(canvas.context) self._position = CanvasCoordinate.origin() self._color = (0, 0, 0, 1)
def test_comparisons(self): assert Cc.from_in(1, 1).pt[0] > Cc.from_cm(1, 1).pt[0] assert Cc.from_cm(1, 1).pt[0] > Cc.from_mm(1, 1).pt[0] assert Cc.from_mm(1, 1).pt[0] > Cc.from_pt(1, 1).pt[0]
def test_eq(self): assert Cc.from_pt(1, 2) == Cc.from_pt(1, 2) assert Cc.from_pt(1, 2) != (1, 2)
def test_init(self): bbox = CanvasBbox(Cc.from_in(1, 2), Cu.from_in(3), Cu.from_in(4)) assert bbox.pos == Cc.from_in(1, 2) assert bbox.width == Cu.from_in(3) assert bbox.height == Cu.from_in(4)