<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="favicon.png">
<title></title>
<meta name="description" content="" />
<link href="style.css" rel="stylesheet" type="text/css" />
<style>
</style>
</head>
<body>
<div id="container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox='0 0 185.95 100' width='743.8px' height='400px'>
<g id="region-GA" fill='#282'>
<path id="GA-0" d="M 67.08 87.26 66.46 85.53 65.66 83.65 66.0 81.23 66.32 78.61 65.64 75.82 66.34 71.1 68.42 68.49 67.32 65.82 65.51 61.14 62.53 46.16 59.26 27.2 68.98 27.34 75.7 27.19 91.39 27.31 90.01 28.59 87.99 31.5 91.39 34.04 93.58 34.97 95.92 39.8 97.42 42.5 101.81 46.1 102.68 47.98 105.67 50.45 107.14 54.04 111.16 57.07 112.06 60.5 112.81 62.16 112.38 63.26 114.73 64.9 115.97 67.7 115.95 70.53 117.1 71.09 119.26 71.85 113.32 80.63 111.38 91.09 108.72 90.82 106.22 89.74 104.7 90.24 104.67 95.33 103.04 96.64 102.11 94.08 81.66 91.89 68.99 91.22 67.08 87.26" stroke="black" />
</g>
<g id="region-SC" fill='#829'>
<path id="SC-0" d="M 119.26 71.85 117.1 71.09 115.95 70.53 115.97 67.7 114.73 64.9 112.38 63.26 112.81 62.16 112.06 60.5 111.16 57.07 107.14 54.04 105.67 50.45 102.68 47.98 101.81 46.1 97.42 42.5 95.92 39.8 93.58 34.97 91.39 34.04 87.99 31.5 90.01 28.59 91.39 27.31 92.65 26.85 99.45 24.21 111.08 24.34 116.98 25.05 117.08 26.41 118.35 25.4 120.32 28.0 120.29 29.79 134.29 29.95 148.39 44.39 142.0 49.95 140.2 55.01 126.36 64.75 119.26 71.85" stroke="black" />
</g>
<g id="region-NC" fill='#136'>
<path id="NC-0" d="M 91.39 27.31 75.7 27.19 75.98 23.93 78.65 22.96 79.52 21.3 81.31 19.42 83.86 19.01 86.83 18.3 89.71 16.96 90.95 15.59 93.36 14.35 93.29 13.22 96.46 11.11 97.48 12.48 102.13 9.54 104.31 9.85 106.25 7.23 108.82 6.55 108.68 4.32 109.0 2.35 104.07 2.59 109.0 2.35 130.26 3.03 155.38 3.1 168.72 2.97 180.66 2.9 182.24 2.89 184.02 18.49 176.0 29.93 162.96 34.47 154.68 43.41 148.39 44.39 134.29 29.95 120.29 29.79 120.32 28.0 118.35 25.4 117.08 26.41 116.98 25.05 111.08 24.34 99.45 24.21 92.65 26.85 91.39 27.31" stroke="black" />
</g>
<g id="region-TN" fill='#304'>
<path id="TN-0" d="M 0.98 26.67 2.42 25.23 2.27 20.74 4.26 18.71 4.75 16.14 6.96 14.63 8.24 12.44 8.37 11.15 9.35 7.34 10.12 5.11 10.44 3.59 28.45 3.73 28.41 1.27 30.91 1.23 31.32 1.95 39.2 1.51 45.99 1.59 53.37 1.72 60.6 2.16 64.23 1.97 75.33 2.64 83.86 2.12 87.51 1.23 88.18 0.98 87.51 1.23 83.86 2.12 102.61 2.66 104.07 2.59 109.0 2.35 108.68 4.32 108.82 6.55 106.25 7.23 104.31 9.85 102.13 9.54 97.48 12.48 96.46 11.11 93.29 13.22 93.36 14.35 90.95 15.59 89.71 16.96 86.83 18.3 83.86 19.01 81.31 19.42 79.52 21.3 78.65 22.96 75.98 23.93 75.7 27.19 68.98 27.34 59.26 27.2 43.07 27.0 27.23 26.99 13.4 26.66 0.98 26.67" stroke="black" />
</g>
</svg>
</div>
</body>
</html>
import sys
import random
from pyproj import Transformer
from shapely.geometry import Polygon, Point, MultiPolygon
import shapefile
from jinja2 import Environment, FileSystemLoader, select_autoescape
env = Environment(
loader = FileSystemLoader("."),
autoescape=select_autoescape()
)
template = env.get_template("template.html")
#for states run python3 shpToSVG.py ne_110m_admin_1_states_provinces_lakes stateborders name postal
#for countries run python3 shpToSVG.py ne_110m_admin_0_countries_lakes countryborders NAME GU_A3
def sortSecond(a):
return a[1]
#cFile = 'ne_110m_admin_1_states_provinces_lakes/ne_110m_admin_1_states_provinces_lakes.shp'
print(sys.argv)
if len(sys.argv) == 1:
sys.argv.append("ne_110m_admin_1_states_provinces_lakes")
sys.argv.append("stateborders")
sys.argv.append("name")
sys.argv.append("postal")
cFile = sys.argv[1]+"/"+sys.argv[1]+".shp"
regionShape = shapefile.Reader(cFile)
fields = regionShape.fields[1:]
idx = 0
for field in fields:
if field[0] == sys.argv[3]:
idx3 = idx
if field[0] == sys.argv[4]:
idx4 = idx
idx+=1
regionPolygons = regionShape.shapeRecords()
regionData = {"regions":{},"width":100,"viewBox":"0 0 100 100"}
cidx = 0
#transformer = Transformer.from_crs("epsg:4326", "epsg:5070")
transformer = Transformer.from_crs("epsg:4326", "epsg:3857")#Web Mercator
regions = ["SC","GA","NC", "TN"]
bounds = [-1000,-1000,-1000,-1000]#left,right,bottom,top
scale = 1
width = 100
for region in regionPolygons:
#print(region.shape.__geo_interface__)
#print(region.record)
geo = region.shape.__geo_interface__
geoType = geo["type"]
props = region.record
regionID = props[idx4]
if regionID not in regions:
continue
paths = []
if geoType == 'Polygon':
coords = geo['coordinates'][0]
path = []
for ii in range(0,len(coords)):
out = transformer.transform(coords[ii][1],coords[ii][0])
out = [out[0],-1*out[1]]
path.append(out)
if bounds[0]==-1000 or out[0]<bounds[0]:
bounds[0] = out[0]
if bounds[1]==-1000 or out[0]>bounds[1]:
bounds[1] = out[0]
if bounds[2]==-1000 or out[1]<bounds[2]:
bounds[2] = out[1]
if bounds[3]==-1000 or out[1]>bounds[3]:
bounds[3] = out[1]
poly = Polygon(coords)
paths.append([path,0])
elif geoType == 'MultiPolygon':
allPolys = []
for i in range(0,len(region['geometry']['coordinates'])):
coords = geo['coordinates'][i][0]
path = []
for ii in range(0,len(coords)):
out = transformer.transform(coords[ii][1],coords[ii][0])
out = [out[0],-1*out[1]]
path.append(out)
if bounds[0]==-1000 or out[0]<bounds[0]:
bounds[0] = out[0]
if bounds[1]==-1000 or out[0]>bounds[1]:
bounds[1] = out[0]
if bounds[2]==-1000 or out[1]<bounds[2]:
bounds[2] = out[1]
if bounds[3]==-1000 or out[1]>bounds[3]:
bounds[3] = out[1]
poly = Polygon(coords)
allPolys.append(poly)
paths.append([path,poly.area])
poly = MultiPolygon(allPolys)
else:
print(geoType)
scale = 100/(bounds[3]-bounds[2])
bounds[0] -= 1/scale
bounds[1] += 1/scale
bounds[2] -= 1/scale
bounds[3] += 1/scale
scale = 100/(bounds[3]-bounds[2])
paths.sort(key=sortSecond,reverse=True)
regionName = props[idx3]
width = round((bounds[1]-bounds[0])*scale,2)
color = "#"+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))
regionData["regions"][regionID]={"name":regionName,"id":regionID,"paths":paths,"fillColor":color}
regionData["width"]=width
regionData["viewBox"]="0 0 "+str(width)+" 100"
for r in regionData["regions"]:
pathsRaw = regionData["regions"][r]["paths"]
regionData["regions"][r]["paths"] = []
for i in range(0,len(pathsRaw)):
path = "M"
lastCoord = [-10,-10]
firstCoord = [-10,-10]
for ii in range(0,len(pathsRaw[i][0])):
x = round((pathsRaw[i][0][ii][0]-bounds[0])*scale,2)
y = round((pathsRaw[i][0][ii][1]-bounds[2])*scale,2)
if ii == 0:
firstCoord = [x,y]
if lastCoord[1]==y and lastCoord[0]==x:
continue
lastCoord = [x,y]
path += " "+str(x)+" "+str(y)
if lastCoord[0] != firstCoord[0] or lastCoord[1] != firstCoord[1]:
path += " "+str(firstCoord[0])+" "+str(firstCoord[1])
regionData["regions"][r]["paths"].append({"id":r+"-"+str(i),"d":path})
print(regionData)
output = template.render(regionData)
with open("index.html", "w") as fh:
fh.write(output)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="favicon.png">
<title></title>
<meta name="description" content="" />
<style>
</style>
</head>
<body>
<div id="container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox='{{viewBox}}' width='{{width * 4}}px' height='400px'>
{% for region in regions %}
<g id="region-{{region}}" fill='{{regions[region]["fillColor"]}}'>
{% for path in regions[region]["paths"] %}
<path id="{{path.id}}" d="{{path.d}}" stroke="black" />
{% endfor %}
</g>
{% endfor %}
</svg>
</div>
</body>
</html>
This repl creates an index.html file that includes SVG shapes for a collection of U.S. states using Python.
To view this on TripleLog, click here.
The packages should automatically install if using Replit. You need pyShp (import shapefile), shapely, pyproj, and jinja2. Install (with dependencies) any that need installing. You can also use fiona or other geo packages, but installing GDAL on Replit seems to not always work.
Use the jinja2 templating to generate the index.html file from template.html. The syntax is similar to nunjucks.
We need to read the shapefile and grab the coordinates for whichever states we want to display. You can choose a different set of states by editing the array with state postal codes. You can also pick different colors for the states.
You can use a different shapefile or geoJSON object, but you will need to adjust some of the early code to make everything work.
A template file called template.html is used to eventually create the index.html file with the final SVG shapes. This file can then be used as a static file, but this repl does not serve the file automatically.