Esempio n. 1
0
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
Esempio n. 2
0
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))
Esempio n. 3
0
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()
Esempio n. 4
0
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()
Esempio n. 5
0
 def dumpall():
     dump(generated_path('sf/lot_building_zoning.geojson'), lots)
     dump(generated_path('sf/building_not_found_for_lot.geojson'),
          not_found)
Esempio n. 6
0
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)
Esempio n. 7
0
    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)
Esempio n. 8
0
        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)
Esempio n. 9
0
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)
Esempio n. 10
0
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)
Esempio n. 11
0
 def dumpall():
     dump(generated_path('sf/lot_zoning.geojson'), lot_zoning)
     dump(generated_path('sf/zone_not_found_for_lot.geojson'), not_found)