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 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)
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)