How to create shapefile from Dynamo

Hey guys and geeks. For the past years I’ve been importing from and exporting to GIS system usually using lots of different tools and methods. For creating shapefiles of such I found FME very usefull but it requires a licence, so for basic thing I just used MAPEXPORT. However, there are several reasons why I was still a bit unhappy:

  • Geometry must exist in the drawing
  • Properties within the export must exist.
  • I needed a script anyway to batch process everything

Now since Python 3 is available and packages like shapely and fiona can be installed I looked into exporting geometry to shapefile using Dynamo. Basically I want to export Civil3D entities geometry straight to shapefile (without exploding or such). Now I’ve come up with the attached solution wich seems to do the trick:

  • Prepare the geometry as lists of Dynamo Points and
  • create objectdata. e.g. get the alignment stations at certain interval and
  • write the points at station including objectdata (in this case the station as data as well as the alignment name). To keep things unique I used a counter for identification.

Because the input is basic Dynamo Points one can easily convert the script to do the same for sampleline locations but also for custom exports of random autocad objects.

My big question: Is this concept usefull or did I mis that particular package or piece of software? I want to implement this on corridors too. For what its worth here the code of the shapewriter and the link to the installation guide for python packages: How to install python packages in Dynamo

import sys
import clr
import os
import datetime

clr.AddReference('AcCoreMgd')
from Autodesk.AutoCAD.ApplicationServices import *

# basic libs for working with spatial geometry
# check https://github.com/DynamoDS/Dynamo/wiki/Customizing-Dynamo%27s-Python-3-installation for howto on installing packages
import fiona
from shapely.geometry import mapping, Point, LineString, Polygon

# input
geometries = IN[0]
fieldnames = IN[1]
data = IN[2]
folder = IN[3]

# prepare output for error catching
err_shapes = []
err_lines = []
err_points = []
err_other = []

if IN[3] == None or os.path.basename(IN[3]) == 'No file selected' or not os.path.exists(IN[3]):
    dir = os.path.dirname(Application.DocumentManager.MdiActiveDocument.Name)
else:
    dir = os.path.abspath(IN[3])
base = os.path.splitext(os.path.basename(Application.DocumentManager.MdiActiveDocument.Name))[0]


# create shapely points for points in obj.points
objects = []
for obj in geometries:
    pointsset = []
    for p in obj:
        pointsset.append(Point(p.X,p.Y))
    objects.append(pointsset)

fields = {}
values = {}

# create dictionary with fields and types
# fieldtypes considered: int, float, datetime, str
if len(data) > len(fieldnames):
    added = ['field'+str(f+1) for f in range(len(data)-len(fieldnames))]
    fieldnames = fieldnames + added

n = 0
conv_list = [] # list of indices to convert to string
for list in data:
    objtype = type(list[0]).__name__
    if objtype in ['int', 'float', 'str']:
        fields[fieldnames[n]] = objtype
    else: 
        fields[fieldnames[n]] = 'str'
        conv_list.append(n)
    n += 1
fields['id'] = 'int'

# create dictionary with records (identity and values per item)
n = 0
for obj in objects:
    objval = {}
    objval['id'] = str(n+1)
    for index in range(len(fields.keys())-1):
        if index in conv_list:
            objval[fieldnames[index]] = str(data[index][n])
        else:
            objval[fieldnames[index]] = data[index][n]
    n += 1
    values[str(n)] = objval

# Define coordinatesystem
crs = fiona.crs.from_epsg(28992)

# Define a point feature geometry with attributes
schemap = {
    'geometry': 'Point',
    'properties': fields,
}
# Define a linestring feature geometry with attributes
schemals = {
    'geometry': 'LineString',
    'properties': fields,
}

# Define a polygon feature geometry with attributes
schemapg = {
    'geometry': 'Polygon',
    'properties': fields,
}

# Write a new Shapefile
with fiona.open(os.path.join(dir, base + '_points.shp'), 'w', 'ESRI Shapefile', schemap, crs=crs) as c:
    n = 1
    for pointsset in objects:
        for point in pointsset:
            c.write({
                'geometry': mapping(point),
                'properties': values[str(n)],
            })
        n += 1

with fiona.open(os.path.join(dir, base + '_lines.shp'), 'w', 'ESRI Shapefile', schemals, crs=crs) as c:
    n = 1
    for pointsset in objects:
        if len(pointsset) < 2:
            err_lines.append('invalid object')
            continue            
        else:
            ls = LineString(pointsset)
            c.write({
                'geometry': mapping(ls),
                'properties': values[str(n)],
            })
        n += 1
    
with fiona.open(os.path.join(dir, base + '_polygons.shp'), 'w', 'ESRI Shapefile', schemapg, crs=crs) as c:
    n = 1
    for pointsset in objects:
        if len(pointsset) < 3:
            err_shapes.append('invalid object')
            continue
        else:
            pg = Polygon(pointsset)
            c.write({
                'geometry': mapping(pg),
                'properties': values[str(n)],
            })
        n += 1

OUT = fields




AlignmentStationsToSHP.dyn (39.4 KB)

4 Likes

Thanks for sharing, @geert.drijfhout!

Excellent job.
Thanks for sharing.