begone, line!

I set the political svgs to remove the divide thru russia at the antimeridian.  I'm a bit surprised I didn't try this sooner since it's just kind of an ugly erroneus line in most of my map projections.  unfortunately, the solution I found doesn't work for the physical maps.  that's a bit of a bummer for the tissot ellipse maps specificly.  however, it was most notable (and most potentially confusing) in the blank political templates, so I'm very glad that I fixd that.
This commit is contained in:
Justin Kunimune 2023-12-21 21:21:02 -10:00
parent 9912d00ae7
commit 98a22a7304
8 changed files with 144 additions and 81 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 MiB

After

Width:  |  Height:  |  Size: 27 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -115,7 +115,7 @@ def main():
' <g transform="matrix(1,0,0,-1,180,90)">\n'
' <rect class="water" x="-180" y="-90" width="360" height="180" />\n'
' <g class="country">\n'
+ plot_political_shapes('ne_50m_admin_0_countries', trim_antarctica=True) +
+ plot_political_shapes('ne_50m_admin_0_countries', trim_antarctica=True, fuse_russia=True) +
' </g>\n'
' <g class="water">\n'
+ plot_shapes('ne_50m_lakes', max_rank=4) +
@ -130,10 +130,10 @@ def main():
' <g transform="matrix(1,0,0,-1,180,90)">\n'
' <rect class="water" x="-180" y="-90" width="360" height="180" />\n'
' <g class="country">\n'
+ plot_political_shapes('ne_110m_admin_0_countries', trim_antarctica=True, add_title=True,
mode="normal")
+ plot_political_shapes('ne_110m_admin_0_countries', trim_antarctica=True, add_title=True,
mode="circle", include_circles_from='ne_10m_admin_0_countries') +
+ plot_political_shapes('ne_110m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
add_title=True, mode="normal")
+ plot_political_shapes('ne_110m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
add_title=True, mode="circle", include_circles_from='ne_10m_admin_0_countries') +
' </g>\n'
' <g class="water">\n'
+ plot_shapes('ne_110m_lakes', max_rank=4) +
@ -151,16 +151,17 @@ def main():
' <g transform="matrix(1,0,0,-1,180,90)">\n'
' <rect class="water" x="-180" y="-90" width="360" height="180" />\n'
' <g class="province">\n'
+ plot_political_shapes('ne_50m_admin_1_states_provinces', trim_antarctica=True, add_title=True,
filter_field="adm0_a3", filter_values=a3_with_provinces) +
+ plot_political_shapes('ne_50m_admin_1_states_provinces', trim_antarctica=True, fuse_russia=True,
add_title=True, filter_field="adm0_a3", filter_values=a3_with_provinces) +
' </g>\n'
' <g class="country-outline">\n'
+ plot_shapes('ne_50m_admin_0_countries', trim_antarctica=True,
+ plot_shapes('ne_50m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
filter_field="adm0_a3", filter_values=a3_with_provinces) +
' </g>\n'
' <g class="country">\n'
+ plot_political_shapes('ne_50m_admin_0_countries', trim_antarctica=True, add_title=True,
filter_field="adm0_a3", filter_values=a3_with_provinces, filter_mode="out") +
+ plot_political_shapes('ne_50m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
add_title=True, filter_field="adm0_a3", filter_values=a3_with_provinces,
filter_mode="out") +
' </g>\n'
' <g class="water">\n'
+ plot_shapes('ne_50m_lakes', max_rank=4) +
@ -174,10 +175,12 @@ def main():
' <g transform="matrix(1,0,0,-1,180,90)">\n'
' <rect class="water" x="-180" y="-90" width="360" height="180" />\n'
' <g class="country">\n'
+ plot_political_shapes('ne_10m_admin_0_countries', trim_antarctica=True, mode="normal") +
+ plot_political_shapes('ne_10m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
mode="normal") +
' </g>\n'
' <g class="thick-country-border">\n'
+ plot_political_shapes('ne_10m_admin_0_countries', trim_antarctica=True, mode="trace") +
+ plot_political_shapes('ne_10m_admin_0_countries', trim_antarctica=True, fuse_russia=True,
mode="trace") +
' </g>\n'
' <g class="river">\n'
+ plot_shapes('ne_10m_rivers_lake_centerlines', max_rank=5) +
@ -193,7 +196,7 @@ def main():
+ plot_shapes('ne_10m_admin_0_boundary_lines_disputed_areas') +
' </g>\n'
' <g class="coastline">\n'
+ plot_shapes('ne_10m_coastline', trim_antarctica=True) +
+ plot_shapes('ne_10m_coastline', trim_antarctica=True, fuse_russia=True) +
' </g>\n'
' <g class="lake">\n'
+ plot_shapes('ne_10m_lakes', max_rank=4) +

View File

@ -6,7 +6,7 @@ from numpy import pi, sqrt, cos, radians, inf
from shapely import Polygon
from helpers import plot, trim_edges, ShapeRecord, \
load_shapes_from_one_place_and_records_from_another, load_shaperecords
load_shapes_from_one_place_and_records_from_another, load_shaperecords, fuse_edges
SIZE_CLASSES = [
'lg', 'md', 'sm', None, None, None]
@ -49,7 +49,8 @@ ISO_A3_TO_A2 = {
def plot_political_shapes(filename, mode="normal",
trim_antarctica=False, add_title=False, include_circles_from=None,
trim_antarctica=False, fuse_russia=False,
add_title=False, include_circles_from=None,
filter_field: Optional[str] = None,
filter_values: Optional[list[Any]] = None, filter_mode="in") -> str:
""" it's like plot_shapes but it also can make circles and handles, like, dependencies and stuff
@ -59,6 +60,7 @@ def plot_political_shapes(filename, mode="normal",
ID and clip to that existing shape, or
"circle" to do circles at the center of mass specificly for small countries
:param trim_antarctica: whether to adjust antarctica's shape
:param fuse_russia: whether to reattach the bit of Russia that's in the western hemisphere
:param add_title: add mouseover text
:param include_circles_from: an additional filename from which to borrow small objects. anything
present in this shapefile but not in the main one will be included
@ -91,8 +93,11 @@ def plot_political_shapes(filename, mode="normal",
# if it Antarctica, trim it
if trim_antarctica:
if record["sov_a3"] == 'ATA':
shape.points = trim_edges(shape.points, shape.parts)
if record["sov_a3"] == "ATA":
shape.points, shape.parts = trim_edges(shape.points, shape.parts)
if fuse_russia:
if record["sov_a3"] == "RUS":
shape.points, shape.parts = fuse_edges(shape.points, shape.parts)
sovereign_code = record["sov_a3"]
if sovereign_code in SOVEREIGN_CODES:
sovereign_code = SOVEREIGN_CODES[sovereign_code] # convert US1 to USA

View File

@ -1,12 +1,12 @@
# read data from a shapefile into SVG format
from helpers import plot, trim_edges, lengthen_edges, load_shaperecords
from helpers import plot, trim_edges, lengthen_edges, load_shaperecords, fuse_edges
def plot_shapes(
filename, max_rank=float('inf'), clazz=None,
trim_antarctica=False, flesh_out_antarctica=False, mark_antarctica=False,
filter_field=None, filter_values=None) -> str:
trim_antarctica=False, flesh_out_antarctica=False, fuse_russia=False,
mark_antarctica=False, filter_field=None, filter_values=None) -> str:
result = ""
for shape, record in load_shaperecords(filename):
if filter_field is not None:
@ -27,7 +27,7 @@ def plot_shapes(
is_antarctica = shape.points[0][1] < -60
if is_antarctica:
if trim_antarctica:
shape.points = trim_edges(shape.points, shape.parts)
shape.points, shape.parts = trim_edges(shape.points, shape.parts)
elif flesh_out_antarctica:
shape.points = lengthen_edges(shape.points)
if mark_antarctica:
@ -35,6 +35,8 @@ def plot_shapes(
clazz_for_this_section = "antarctic"
elif "antarctic" not in clazz_for_this_section:
clazz_for_this_section += " antarctic"
if fuse_russia:
shape.points, shape.parts = fuse_edges(shape.points, shape.parts)
result += plot(shape.points, midx=shape.parts, close=False,
clazz=clazz_for_this_section, fourmat='xd')

View File

@ -4,7 +4,8 @@ import random as rng
from typing import Any, Iterable, Optional
import shapefile
from numpy import pi, sin, cos, tan, arcsin, arccos, degrees, ceil, radians, arctan2, hypot
from matplotlib import pyplot as plt
from numpy import pi, sin, cos, tan, arcsin, arccos, degrees, ceil, radians, arctan2, hypot, cumsum, transpose
def load_shaperecords(filename) -> Iterable[ShapeRecord]:
@ -125,15 +126,67 @@ def plot(coords: list[tuple[float, float]], midx: Optional[list[int]] = None, cl
def trim_edges(coast, coast_parts):
"""remove the extra points placed along the edges of the Plate Carree map"""
for i in range(len(coast)-1, -1, -1):
if i in coast_parts:
continue
x0, y0 = coast[i-1] if i > 0 else coast[i]
x1, y1 = coast[i]
x2, y2 = coast[i+1] if i < len(coast)-1 else coast[i]
if (i not in coast_parts) and (abs(x0) > 179.99 or abs(y0) > 89.99) and (abs(x1) > 179.99 or abs(y1) > 89.99) and (abs(x2) > 179.99 or abs(y2) > 89.99):
if (abs(x0) > 179.99 or abs(y0) > 89.99) and \
(abs(x1) > 179.99 or abs(y1) > 89.99) and \
(abs(x2) > 179.99 or abs(y2) > 89.99):
coast.pop(i)
for j in range(len(coast_parts)):
if coast_parts[j] > i:
coast_parts[j] -= 1
return coast
return coast, coast_parts
def fuse_edges(points: list[tuple[float, float]], part_indices: list[int]) -> tuple[list[tuple[float, float]], list[int]]:
"""look for parts with matching vertices on either side of the antimeridian and combine them"""
# first, we must apply the trimming algorithm.
points, parts = trim_edges(points, part_indices)
# break the data up into the individual parts
parts = []
for i in range(len(part_indices)):
start = part_indices[i]
end = part_indices[i + 1] if i + 1 < len(part_indices) else len(points)
parts.append(points[start:end])
if points[start] == points[end - 1]:
parts[-1] = parts[-1][:-1] # also remove the duplicate endpoint
# look for pairs of parts with matching antimeridianal points, and stitch them together
parts = fuse_edges_of_parts(parts)
# put it all back into a single list of points and a list of moveto indices
for part in parts:
part.append(part[0]) # don't forget to undo the part where we removed the duplicate endpoint
points = sum(parts, start=[])
part_indices = cumsum([len(part) for part in parts])
part_indices = [0] + list(part_indices[:-1])
return points, part_indices
def fuse_edges_of_parts(parts: list[list[tuple[float, float]]]) -> list[list[tuple[float, float]]]:
"""look for parts with matching vertices on either side of the antimeridian and combine them"""
for i in range(len(parts)):
# look at every point in part i
for j in range(0, len(parts[i])):
# search for any that are on the antimeridian, and after another on the antimeridian
if abs(parts[i][j][0]) > 179.99 and abs(parts[i][j - 1][0]) > 179.99:
for k in range(i):
# look at every point in part k
for l in range(len(parts[k])):
# search for any that are on the antimeridian, and after another on the antimeridian
if abs(parts[k][l][0]) > 179.99 and abs(parts[k][l - 1][0]) > 179.99:
# if the two points' latitudes match
if abs(parts[i][j - 1][1] - parts[k][l][1]) < 0.01:
# do the stitching
parts[i] = parts[i][:j] + parts[k][l:] + parts[k][:l] + parts[i][j:]
parts.pop(k)
# recurse in case we need to stitch together a few in a row
return fuse_edges_of_parts(parts)
# return the unmodified list if we found absolutely noting to fuse
return parts
def lengthen_edges(coast):