def main(): objs = load(data_path('zoning_height_2000.geojson')) new_zones = defaultdict(int) for i, o in enumerate(objs): prop = o['properties'] zoning_code = prop['zoning'] if zoning_code is None: prop['fill'] = UNKNOWN prop['homes'] = 0 continue height_code = prop['height_lim'] height = parse_height(height_code) if height_code is not None else None if height is not None: homes_height = units_per_height(height_code, height, zoning_code) else: homes_height = 1e9 zoning_code = zoning_code.split('/')[0].strip() try: homes_zoning = units_per_density_limit(zoning_code, waiverless_adus=False) except NewZoneDetected: area = sq_ft(o['geometry']) new_zones[zoning_code] += area homes_zoning = 0 print(prop) homes = min(homes_zoning, homes_height) prop['homes'] = homes prop['homes_zoning'] = homes_zoning prop['homes_height'] = homes_height prop['fill'] = color(homes) if homes_zoning != -999 else UNKNOWN stats = key_stats(objs, lot_size=LOT_SIZE, all_area_denom=True) stats['city'] = 'San Francisco' stats['center'] = [-122.42936665634733, 37.75967613988033] stats['zoom'] = 11.75 os.makedirs(generated_path('sf2000'), exist_ok=True) dump('generated/sf2000/density_map.geojson', objs) with open(generated_path('sf2000/key_data.json'), 'w') as f: json.dump(stats, f) if new_zones: print('New Zones: \n\t' + '\n\t'.join(new_zones.keys())) print(new_zones) return -1
def main(): with open(generated_path('sf/zoning_height.geojson')) as f: obj = json.load(f) new_zones = set() l = obj['features'] for o in l: coords = o['geometry']['coordinates'] prop = o['properties'] try: homes_zoning = units_per_density_limit(prop['zoning']) except NewZoneDetected: new_zones.add(prop['zoning']) homes_height = units_per_height(prop['height_str'], prop['height'], prop['zoning']) homes = min(homes_zoning, homes_height) prop['homes'] = homes prop['homes_zoning'] = homes_zoning prop['homes_height'] = homes_height prop['fill'] = color(homes) if new_zones: print('New Zones: \n\t' + '\n\t'.join(new_zones)) return -1 stats = key_stats(l, lot_size=LOT_SIZE, all_area_denom=True) stats['city'] = 'San Francisco' stats['center'] = [-122.42936665634733, 37.75967613988033] stats['zoom'] = 11.75 os.makedirs(generated_path('sf'), exist_ok=True) with open(generated_path('sf/key_data.json'), 'w') as f: json.dump(stats, f) with open(generated_path('sf/density_map.geojson'), 'w') as f: json.dump(obj, f) print('Illegal to build apartment building in %s%% of SF' % round(stats['apt_illegal_pct'], 1)) print('Illegal to build building with > 5 units in %s%% of SF' % round(stats['apt5_illegal_pct'], 1)) print( 'Illegal to build affordable housing in %s%% of SF land zoned for residential' % round(stats['affordable_illegal_resi_pct'], 1)) print('Illegal to build affordable housing in %s%% of SF' % round(stats['affordable_illegal_pct'], 1))
def main(): zones = load(generated_path('sf/density_map.geojson')) lots = load(data_path('lots.geojson')) index = PolygonIndex() for i, obj in enumerate(zones): print('%s / %s' % (i + 1, len(zones))) index.add_obj(obj) lot_zoning = [] not_found = [] def dumpall(): dump(generated_path('sf/lot_zoning.geojson'), lot_zoning) dump(generated_path('sf/zone_not_found_for_lot.geojson'), not_found) for i, obj in enumerate(lots): print('%s / %s' % (i + 1, len(lots))) # commented out because we don't want partial results to be accepted # if i % 10000 == 0: # dumpall() lot_poly = shape(obj['geometry']) intersecting = index.intersecting(lot_poly) obj['properties']['address'] = address(obj['properties']) obj['properties']['sqft'] = sq_ft(obj['geometry']) intersects = [] for zone in intersecting: intersect = lot_poly.intersection(zone.polygon) if intersect.is_empty: continue intersects.append((zone.data['properties'], intersect.area)) if not intersects: # this is probably a vacant thingy print('ZONE NOT FOUND', obj) not_found.append(obj) continue max_i = max(intersects, key=lambda x: x[1]) print(max_i[0]['homes'], obj['properties']['resunits']) obj['properties']['zoning'] = max_i[0] lot_zoning.append(obj) dumpall()
def main(): lots = load(generated_path('sf/lot_zoning.geojson')) buildings = load(data_path('buildings.geojson')) index = PolygonIndex() for i, obj in enumerate(lots): print('%s / %s' % (i + 1, len(lots))) index.add_obj(obj) not_found = [] def dumpall(): dump(generated_path('sf/lot_building_zoning.geojson'), lots) dump(generated_path('sf/building_not_found_for_lot.geojson'), not_found) for i, obj in enumerate(buildings): print('%s / %s' % (i + 1, len(buildings))) poly = shape(obj['geometry']) poly = poly.buffer(0) # fix self-intersection polygons intersecting = index.intersecting(poly) intersects = [] for lot in intersecting: intersect = poly.intersection(lot.polygon) if intersect.is_empty: continue intersects.append((lot.data, intersect.area)) if not intersects: print('LOT NOT FOUND', obj) not_found.append(obj) continue print(len(intersects)) if len(intersects) != 1: for x in intersects: if poly.area: print('\t', 100.0 * x[1] / poly.area) max_i = max(intersects, key=lambda x: x[1]) max_i[0]['properties'].setdefault('buildings', []).append(obj) dumpall()
def dumpall(): dump(generated_path('sf/lot_building_zoning.geojson'), lots) dump(generated_path('sf/building_not_found_for_lot.geojson'), not_found)
def main(): lots = load(generated_path('sf/lot_building_zoning.geojson')) new_lots = [] too_tall = too_tall_nonwarehouse = 0 for lot in lots: prop = lot['properties'] zoning = prop['zoning'] # must be a residential zone if prop['zoning']['homes'] <= 0: continue # cannot have any homes on it if float(prop['resunits']) > 0: continue # must be at least 10k sq ft if prop['sqft'] < 10e3: continue # affordable devs won't build taller than 85ft due to cost height = min(zoning['height'], 85) homes_zoning = units_per_density_limit(zoning['zoning'], lot_size=prop['sqft']) homes_height = units_per_height(zoning['height_str'], height, zoning['zoning'], lot_size=prop['sqft']) oldhomes = min(homes_zoning, homes_height) newhomes = units_per_height('', height, '', prop['sqft']) # affordable projects need at least 50 homes to get funding if newhomes < 50: continue # skip lots that could have built affordable housing already if oldhomes >= 50: continue # make it bright green prop['fill'] = '#7eb100' prop['opacity'] = 0.7 max_height = 0 for building in prop.get('buildings', []): height_ft = METER_TO_FEET * float( building['properties']['hgt_mediancm']) / 100.0 max_height = max(max_height, height_ft) print('\t', height_ft) prop['max_building_height'] = max_height prop['oldhomes'] = oldhomes prop['newhomes'] = newhomes if 'buildings' in prop: del prop['buildings'] CANNOT_DEMOLISH = { 'fill': '#c60003', 'opacity': 0.2, } # demolishing non-warehouse buildings taller than 35ft is too expensive if prop['landuse'] != 'PDR' and max_height > 35: too_tall_nonwarehouse += 1 prop['infeasible_reason'] = ( 'demolishing non-warehouse buildings taller than 35ft is too expensive' ) prop.update(CANNOT_DEMOLISH) # demolishing buildings taller than 50ft is too expensive if max_height > 50: too_tall += 1 prop['infeasible_reason'] = ( 'demolishing buildings taller than 50ft is too expensive') prop.update(CANNOT_DEMOLISH) new_lots.append(lot) print(newhomes, oldhomes, prop['sqft'], prop['zoning']['height']) print(len(new_lots), too_tall, too_tall_nonwarehouse) dump(generated_path('sf/prop_e.geojson'), new_lots)
prop = o['properties'] zoning = prop['PRODGISGeneralPlanDesignationLANDUSECODE'] homes_density = units_per_density_limit(zoning) homes_height = units_per_height(zoning) homes = min(homes_density, homes_height) if 0 < homes < 1: homes = 1 prop['zoning'] = zoning prop['homes'] = homes prop['homes_zoning'] = homes_density prop['homes_height'] = homes_height prop['fill'] = color(homes) print(zoning, round(min(homes_density, homes_height), 1)) stats = key_stats(obj['features'], lot_size=LOT_SIZE, all_area_denom=False) stats['city'] = 'Mountain View' stats['center'] = [-122.0637322335084, 37.39644593970458] stats['zoom'] = 12 os.makedirs(generated_path('mountain_view'), exist_ok=True) with open(generated_path('mountain_view/key_data.json'), 'w') as f: json.dump(stats, f) with open(generated_path('mountain_view/density_map.geojson'), 'w') as f: json.dump(obj, f)
intersect = p.intersection(zp) if not intersect.is_empty: l = (list(intersect) if intersect.geom_type == 'GeometryCollection' else [intersect]) for x in l: if x.area > 0: res.append( Result(polygon=x, properties=dict( zoning=zone.zoning, zoning_sim=zone.zoning_sim, height=height, height_str=height_str, height_index=i, zone_index=zone.index, ))) obj = [] for r in res: obj.append( dict( type='Feature', properties=r.properties, geometry=shapely.geometry.geo.mapping(r.polygon), )) os.makedirs(generated_path('sf'), exist_ok=True) with open(generated_path('sf/zoning_height.geojson'), 'w') as f: json.dump(dict(type='FeatureCollection', features=obj), f)
def main(): features = load(generated_path('sf/lot_building_zoning.geojson')) r = Results() r.num_lots = len(features) r.num_units = 0 r.num_illegal_lots = 0 r.num_illegal_units = 0 r.num_units_in_illegal_building = 0 illegal_homes = [] nozones = [] reason_count = defaultdict(int) lot_sizez = {} for i, obj in enumerate(features): print(i + 1, '/', len(features)) prop = obj['properties'] zoning = prop.get('zoning') if not zoning: nozones.append(obj) continue units = int(prop['resunits']) r.num_units += units if units == 0: continue area = sq_ft(obj['geometry']) min_area = 4000 if zoning['zoning'] == 'RH-1(D)' else 2500 allowed_units_density = units_per_density_limit(zoning['zoning'], lot_size=area, per_lot_size=False, waiverless_adus=False) if allowed_units_density < 0: continue max_median_height = (max( float(b['properties']['hgt_mediancm']) for b in prop['buildings']) / 30.48 if prop.get('buildings') else 0) illegal_units = 0 reasons = [] if area + AREA_BUFFER < min_area: lot_sizez[area] = obj illegal_units = units reason_count['lot too small'] += units reasons.append('lot too small') if units > allowed_units_density: illegal_units = max(illegal_units, units - int(allowed_units_density)) reason_count['too dense'] += units - int(allowed_units_density) if allowed_units_density == 0: reasons.append('no housing allowed here') else: reasons.append('too dense') if max_median_height - HEIGHT_BUFFER > zoning['height']: total_building_volume = sum( (float(b['properties']['hgt_mediancm']) / 30.48) * sq_ft(b['geometry']) for b in prop['buildings']) # calulate how many units each building would allow if its height # were chopped off at the zoning height (assuming even density of # units per building volume for each lot - definitely an incorrect # assumption but it's the best guess we can make) allowed_units = 0 for b in prop['buildings']: building_height = float( b['properties']['hgt_mediancm']) / 30.48 building_area = sq_ft(b['geometry']) units_proportion = units * building_area * building_height / total_building_volume if building_height - HEIGHT_BUFFER > zoning['height']: allowed_units += units_proportion * zoning[ 'height'] / building_height else: allowed_units += units_proportion allowed_units = int(round(allowed_units)) if allowed_units < units: illegal_units = max(illegal_units, units - allowed_units) reason_count['too tall'] += units - allowed_units reasons.append('too tall') if illegal_units > 0: r.num_illegal_lots += 1 r.num_illegal_units += illegal_units r.num_units_in_illegal_building += units obj['properties'] = { 'address': address(prop), 'units': units, 'illegal_units': illegal_units, 'allowed_units_density': allowed_units_density, 'zoning_code': zoning['zoning'], 'allowed_height': zoning['height'], 'max_median_height': max_median_height, 'lot_sq_ft': area, 'minimum_lot_sq_ft': min_area, 'fill': color(illegal_units), 'reasons': ', '.join(reasons), 'year_built': int(prop.get('yrbuilt')) or 'unknown', } illegal_homes.append(obj) print(r.results()) key = [] for i, c in enumerate(COLORS): key.append({ 'num': i + 1, 'color': c, }) key_data = { 'key': key, 'reason_counts': reason_count, } key_data.update(r.asdict()) assert not nozones dump('generated/sf/illegal_homes.geojson', illegal_homes) with open(generated_path('sf/illegal_homes_key_data.json'), 'w') as f: json.dump(key_data, f)
from lib.fileutil import load, dump, generated_path, data_path from lib.calc.sf import address FILENAME = 'sf/lot_building_zoning.geojson' lots = load(generated_path(FILENAME)) for lot in lots: prop = lot['properties'] new_address = address(prop) print(prop['address'], '->', new_address) prop['address'] = new_address dump(generated_path(FILENAME), lots)
def dumpall(): dump(generated_path('sf/lot_zoning.geojson'), lot_zoning) dump(generated_path('sf/zone_not_found_for_lot.geojson'), not_found)