Custom Parcels/Lots or Subdivision Tools

4830
8
07-20-2017 04:06 PM
BrendanBuchanan_Dee
New Contributor II

I'm running into an issue where the block subdivision types in CityEngine does not seem to have the functionality I need for my current project. I'm looking to write a rule that allows for greater control in the parceling stage. I need to be able to create a controlled mosaic of zoning types within a block and have a unique parceling for each of those different zoning types. I currently have a CGA rule that allows me to split the block in two (A and B) and then have additional splits within A and B and the ability to rotate the orientation of A and B. Each of the subblock areas can then be assigned a zoning type (color as placeholders in the images). Once I'm at this stage I cannot select individual parcels, unlike those created using one of the existing subdivision types. Additionally, the polygons I've created are not aware of street edges and do not have the option to Set First Edge. After assigning a zoning type the subblock area should be divided into parcels according to specific zoning parameters (minimum & maximum lot widths, setbacks, etc) as drawn in image 4.  

The other option I'm considering is using roads with widths of 0m to subdivide the whole block into several CityEngine blocks so that I can preserve the Set First Edge function. I'd like to avoid this option if possible as I want to have dynamic sliders that would allow users configure subblock zoning dimensions. 

0 Kudos
8 Replies
MatthiasBuehler3
Occasional Contributor

Hi Brendan,

For this, you'd have to write pretty complex CGA code to encode all the design functionality you need in an efficient manner.

The 'Dynamic City Layouts' toolset is quite limited in this regard.

Maybe we can help with more advanced CGA code.

Have a great day!

matt

------------------------------------------------------------------------------------------------------------------
matthias buehler || ceo & founder || dipl. arch. eth zurich
------------------------------------------------------------------------------------------------------------------

vrbn AG – official esri partner
|| www.vrbn.io || winterthurerstrasse 53 || 8610 uster || switzerland
+41 78 930 04 07 || matthias.buehler@vrbn.io || @vrbnio @mattb3d

AbeleGiandoso
Occasional Contributor

What kind of complex CGA would make it possible while keeping it dynamic?

The 'Dynamic City Layouts' toolset is super limited, but it's the only way I know of that calculates the Streets Edges automatically, sadly.

If I do it in CGA, all the final lot shapes share the same street edge of the original block instead of having their own dedicated one (lots in the center of the block should not have a street edge).

It would be SO awesome to be able to have more control on the Dynamic subdividsub of blocks into lots, something where you can specify the percentage of occurrence of big lots, medium and small lots..

Right now you can make the lots less regular in size, but big and small lots appear at random and in a even number, while it is quite more probable that there are a few giant buildings and many small houses.

0 Kudos
BrendanBuchanan_Dee
New Contributor II

Hi Matt,

I've written a CGA rule that allows the user to control the splitting of a block into a series of shapes. Once the final subblock units have been set I've used a python script to convert them to static parcels. One problem I'm having is saving the area of each of these areas as an object attribute in the newly created shapes (for subsequent FAR calculations). Once they are static shapes I need to manually remove some excess shapes (there are extra subblock areas created for each rectangle in the block, including the block itself). Finally, I've tried writing an Export (Reporting) Python module that creates a new object attribute for each subblock unit and assigns the area to that attribute.  

Any advice you can provide is greatly appreciated!

Brendan

_______________________________________________________________________________________

CGA rule for controlled subdivision of a block: 

/**
* File: subblock.cga
* Created: 20 Jul 2017 18:58:27 GMT
* Author: bcbd
*/

version "2017.0"

import Zoning : "Zoning.cga"

@hidden
attr myZoning = ""


attr zoningResidentialArea = 0
attr zoningCommercialArea = 0
attr zoningInstitutitonalArea = 0
attr zoningMixed_UseArea = 0
attr zoningIndustrialArea = 0

@Group("Initial Block: First Split", 1)
attr A_B_Split = false
attr rotate_AB = false
@Range(0,200)
attr A_Depth = 20

@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB = "Residential"

@Group("A: Second Split", 3)
attr A1_A2_Split = false
attr rotate_ZoneA = false
@Range(0,100)
attr A2_Width = 20

attr A3_Split = false
@Range(0,100)
attr A3_Width = 20

@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA1 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA2 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA3 = "Residential"


@Group("B: Second Split", 4)

attr B1_B2_Split = false
attr rotate_ZoneB = false
@Range(0,100)
attr B2_Width = 20

attr B3_Split = false
@Range(0,100)
attr B3_Width = 20


@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB1 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB2 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB3 = "Residential"


////////////////////////// BEGINNING OF SPLIT
@StartRule
block -->
case A_B_Split == true :
case rotate_AB == false :
split(x) {A_Depth : zoneA | ~1 : zoneB}
Reporting
NIL ///////////////////
else :
split(z) {A_Depth : zoneA | ~1 : zoneB}
Reporting ///////////////////
NIL
else :
colorPicker(zoningA)
Reporting ///////////////////
NIL


zoneA -->
case A1_A2_Split == true && A3_Split == false :
case rotate_ZoneA == false :
split(x) {A2_Width : zoneA1 | ~1 : zoneA2}
Reporting ///////////////////
NIL
else :
split(z) {A2_Width : zoneA1 | ~1 : zoneA2}
Reporting ///////////////////
NIL
case A1_A2_Split == true && A3_Split == true :
case rotate_ZoneA == false :
split(x) {A2_Width : zoneA1 | ~1 : zoneA2 | A3_Width : zoneA3}
Reporting ///////////////////
NIL
else :
split(z) {A2_Width : zoneA1 | ~1 : zoneA2 | A3_Width : zoneA3}
Reporting ///////////////////
NIL

else :
colorPicker(zoningA)
Reporting ///////////////////
NIL


zoneB -->
case B1_B2_Split == true && B3_Split == false :
case rotate_ZoneB == false :
split(x) {B2_Width : zoneB1 | ~1 : zoneB2}
Reporting ///////////////////
NIL
else :
split(z) {B2_Width : zoneB1 | ~1 : zoneB2}
Reporting ///////////////////
NIL
case B1_B2_Split == true && B3_Split == true :
case rotate_ZoneB == false :
split(x) {B2_Width : zoneB1 | ~1 : zoneB2 | B3_Width : zoneB3}
Reporting ///////////////////
NIL
else :
split(z) {B2_Width : zoneB1 | ~1 : zoneB2 | B3_Width : zoneB3}
Reporting ///////////////////
NIL

else :
colorPicker(zoningB)
Reporting ///////////////////
NIL

#ZONE A
zoneA1 -->
colorPicker(zoningA1)

zoneA2 -->
colorPicker(zoningA2)

zoneA3 -->
colorPicker(zoningA3)

#ZONE B
zoneB1 -->
colorPicker(zoningB1)

zoneB2 -->
colorPicker(zoningB2)

zoneB3 -->
colorPicker(zoningB3)

colorPicker(zoningType)-->
case zoningType == "Residential" :
Zoning.Residential
label("Residential")
set(myZoning, "Residential")
//set(zoningResidentialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Commercial" :
Zoning.Commercial
set(myZoning, "Commercial")
label("Commercial")
//set(zoningCommercialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Institutional" :
Zoning.Institutional
label("Institutional")
set(myZoning, "Institutional")
//set(zoningInstitutitonalArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Mixed_Use" :
Zoning.Mixed_Use
label("Mixed_Use")
set(myZoning, "Mixed_Use")
//set(zoningMixed_UseArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Industrial" :
Zoning.Industrial
label("Industrial")
set(myZoning, "Industrial")
//set(zoningIndustrialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
else : NIL

Reporting -->
report("Area_Residential", zoningResidentialArea)
report("Area_Commercial", zoningCommercialArea)
report("Area_Institutional", zoningInstitutitonalArea)
report("Area.Mixed_Use", zoningMixed_UseArea)
report("Area.Industrial", zoningIndustrialArea)

_______________________________________________________________________________________

Script to convert selected block into static subblock fragments:

'''
Created on 2017-07-26

@author: bcbd
'''
from scripting import *

# get a CityEngine instance
ce = CE()

def convertModelsToShapes():
models = ce.selection()
shapes = ce.convertModelsToShapes(models)
newShapes = ce.separateFaces(shapes)

#cleanupSettings = CleanupShapesSettings()
#LIST ALL CLEANUP SETTINGS TO BE APPLIED
#cleanupSettings.setRemoveCoplanarEdges(True) #DOES THIS EVEN WORK?

#cleanedShapes = ce.cleanupShapes(newShapes,cleanupSettings)

#print "new shape converted from model: "+ce.getName(newShapes[0])

def subblockSelection():
shapes = ce.getObjectsFrom(ce.scene, ce.isShape)

if __name__ == '__main__':
convertModelsToShapes()

pass

 

_______________________________________________________________________________________

Once excess subblock units are removed (currently manual):

Script to calculate parcel area:

attr parcelArea_Calc = geometry.area

Lot -->
set(parcelArea_Calc, geometry.area)
report("Parcel_Area", parcelArea_Calc)
print("Parcel Area: " + parcelArea_Calc)
color(0.2,0.2,0.2) #color orange if CGA rule is complete

_______________________________________________________________________________________

Script to generate new object attributes (parcel area) for each subblock unit. Not yet complete. 

'''
Created on 2017-07-27

@author: bcbd
'''
from scripting import *

# Get a CityEngine instance
ce = CE()

REPORT = ""

def initExport(exportContextOID):
global REPORT
REPORT = "Name,parcelArea\n"
parcelArea()

# Called for each shape after generation.
def finishModel(exportContextOID, shapeOID, modelOID):
shape = Shape(shapeOID)
model = Model(modelOID)

global REPORT
i = 0
totalArea = 0.0
reports = model.getReports()
if 'Parcel_Area' in reports.keys():
for area in reports['Parcel_Area']:
#totalArea +=area
i+=1
REPORT += "%s,%d\n" % (ce.getName(shape), area)
#ce.addAttributeLayer(model, 'parcelArea', area)
else :
REPORT += "%s,%d\n" % (model, 0)

def finishExport(exportContextOID):
filename = ce.toFSPath("models/"+"/reportdata.txt")
FILE = open(filename, "w")
FILE.write(REPORT)
FILE.close()

'''
if(model.getReports().has_key('Parcel_Area')):
l = len(model.getReports()['Parcel_Area'])
for i in range(0,l):
parcelArea = model.getReports()['Parcel_Area']
l = ce.addAttributeLayer('Parcel_Area')
ce.setAttribute(shape, "/ce/rule/Parcel_Area", parcelArea)

# Called after all shapes are generated.
def finishExport(exportContextOID):
ctx = ScriptExportModelSettings(exportContextOID)
parcelArea()
'''
def parcelArea():
objects = ce.getObjectsFrom(ce.selection, ce.isShape)
for o in objects:
ce.setRuleFile(ce.selection(), 'calculateParcelArea.cga') # assign CGA rule calcParcelArea
ce.setStartRule(ce.selection(), 'Lot')
ce.generateModels(ce.selection())






0 Kudos
MatthiasBuehler3
Occasional Contributor

hey,

well, the overall strategy you chose seems feasible, but of course using Python makes things a lot more complex... I'm not sure what you precisely need to reach, but can't you add further CGA attrs for more control deeper down in CGA?

Greets,

matt

0 Kudos
BrendanBuchanan_Dee
New Contributor II

Hey Matt,

I understand the Python will make things more complex but I believe it will be necessary to automate many parts of my project. I’m interested in using CityEngine to model scenarios in two ways. Firstly, what metrics we can be reported from building form, such a FAR, passive and active zones, and thermal shape factor. Secondly, how can metrics be used to generate form, such as setting FAR and having CityEngine produce a variety of building forms that would suit the FAR condition. So far the most successful and flexible example that I’ve come across is your tutorial “GFA & FAR Calculations with CityEngine” on Youtube: https://www.youtube.com/watch?v=gnefAyqUQVE&t=2503s&index=3&list=PL34n_T3uZ_mMzFk4aV7Lo6EgxoQGZEcSg

Around 33:15 in the tutorial you made a new parcel polygon beneath your building, applied calcArea.cga, and manually added the parcel area printed to the console as a new object attribute. At 37:15 you mention that “object attributes cannot be set directly with CGA” and that a Python script might be used to keep the value up-to-date. I’ll be working with hundreds of parcels so I want to write a Python script. From what I understand I need to use a Python exporting (reporting) script to report parcel area values, automate the creation of a new object attribute (parcelArea) for each parcel shape and assign parcelArea from the reported value. Is this correct?

Thanks,

Brendan

0 Kudos
MatthiasBuehler3
Occasional Contributor

Hi,

wow, this video is still being watched .. seems I did something right!

re your last question:

yes, that's exactly correct.

thing is:

shape + rule ==> model

this is a one way street: CGA can NOT modify the initial shape or it's initial attribution.

--> to change the attribution of the shape based on metric coming from the model, the only way is using the python script based exporter ..

so I think you're on the right track.

cheers,

matt

------------------------------------------------------------------------------------------------------------------
matthias buehler || ceo & founder || dipl. arch. eth zurich
------------------------------------------------------------------------------------------------------------------

vrbn AG – official esri partner
|| www.vrbn.io || winterthurerstrasse 53 || 8610 uster || switzerland
+41 78 930 04 07 || matthias.buehler@vrbn.io || @vrbnio @mattb3d

BrendanBuchanan_Dee
New Contributor II

Hi Matt,

Thanks for your help. I was having trouble with the Python export (reporting) module so I ended up writing a script that uses Gauss' polygon area formula/shoelace formula to batch calculate the area of polygons. It uses the ce.getVertices function to retrieve the unstructured vertices list from a polygon [x1, y1, z1, x2, y2, z2,... xN, yN, zN] and converts it to the format [[x1, z1], [x2, z2],...[xN, zN]]. The polygon area is then saved as an object attribute to the original shape.

_____________________________________________________________________________________________

'''
Created on 2017-08-03
@author: bcbd

SELECT ANY NUMBER OF 2D POLYGONS AND HIT F9
RETURNS THE CALCULATED AREA OF THE POLYGON, STORED AS AN OBJECT ATTRIBUTE

'''
from scripting import *

# get a CityEngine instance
ce = CE()

def toShape(): 
    shapes = ce.getObjectsFrom(ce.selection, ce.isShape)
    for shape in shapes:
       ce.setRuleFile(ce.selection(), 'calculateParcelArea.cga') # assign CGA rule calcParcelArea
       ce.setStartRule(ce.selection(), 'Lot')
       ce.generateModels(ce.selection())
       parcelArea = toParcelArea()
       ce.setAttribute(shape, 'parcelArea', parcelArea) 

def toParcelArea(): 
    parcels = ce.getObjectsFrom(ce.selection, ce.isShape) 
    for parcel in parcels: 
    
       #TO CALCULATE PARCEL AREA
       XYZ_coords = ce.getPosition(parcel) # GETS THE CENTROID COORDINATES FOR EACH PARCEL
       vertices = ce.getVertices(parcel) #retrieve unstructured list with coordinates of vertices
       verticesRestructured = restructureList(vertices) 
       parcelArea = polygonArea(verticesRestructured)
       ce.setAttribute(parcel, 'parcelArea', parcelArea)
 
def restructureList(vertices):
    results = [] #create empty list to store restructured list [[x1,z1],[x2,z2],[x3,z3]]
    print "Unstructured list in format: [x1,y1,z1,x2,y2,z2,x3,y3,z3...xN,yN,zN]"
    print vertices
    i = 0 #initialize counter for first while loop
    j = 0 #initialize counter for second while loop
    coord_XZ = [] #create empty coord pair list to store each coord pair [x1,z1]
    while i < len(vertices): # while the counter is less than the length of the unstructured list 
       while j <= 2: #while the counter is less than equal to 2 (for unstructured X, Y, and Z coordinates 
          coord_XZ.append(vertices) #adds the value from unstructured list to coord pair list
          i = i + 1
          j = j + 1
       j = 0 
       del coord_XZ[1] #removes the Y (height) coordinate from the vertex 
       results.append(coord_XZ) #appends X,Y coordinates for one vertex 
       coord_XZ = [] #RESET COORD LIST TO NULL doesnt work
       #print "------" #reset 
       print "Restructured list in format: [[x1,z1], [x2,z2], [x3,z3]...[xN,zN]]"
       print results
       print "-----------------"
       print "The area of the polygon is: "
       print polygonArea(results)
       print "-----------------"
       return results 
 
def polygonArea(corners): #GAUSSIAN POLYGON AREA FUNCTION
    n = len(corners) # of corners
    area = 0.0
    for i in range(n):
       j = (i + 1) % n
       area += corners[0] * corners[1]
       area -= corners[0] * corners[1]
    area = abs(area) / 2.0 
    return area
 
 
if __name__ == '__main__':
 toParcelArea()
 
 pass
0 Kudos
CherylLau
Esri Regular Contributor

Maybe this doesn't work for you, but would it be possible to put a rule on top of your parcel shapes which sets an attribute to store the parcel area and then create the different buildings on that parcel?  For example, in the rule, you could split a parcel into 4 lots (or n lots), where each lot gets a building.  The buildings could be created using attributes for setbacks or height.  These attributes can be set randomly (or by some schema) in the rule for each of the 4 buildings.  Then, the user can change the height or setbacks for each of the 4 buildings using local edits if desired.  If all 4 buildings are created in the same rule (at the parcel level), then you have access to the parcel area, and you wouldn't need to run python scripts to store reported values in object attributes.  With local edits in 2017.0, you will have to create a handle for each attribute you want to be able to change locally.  It's just an idea, and I haven't tried this out yet, so maybe there are caveats that I haven't thought about.

Local edits tutorial:

Tutorial 20: Local edits—CityEngine Tutorials | ArcGIS Desktop 

As for the Python way, I wrote a response to your question here:

https://community.esri.com/message/704045-using-python-to-create-an-object-attribute-from-a-rule-att...