dc-l1-capetown-waterway-nulls

Status: done
Score: 59
Duration: 0:54
Cost: 0.92¢
Model: google/gemma-4-26b-a4b-it

Map (reference ← swipe → agent)

0:00
Hi, first time asking for help with this. The contractor delivered capetown_waterways.geojson but some features are unusable for our mapping pipeline. Drop any features that cannot represent a valid, usable waterway (a feature needs both a drawable geometry and a proper type to be useful) and keep the rest. I'd also like a top-level dropped_count so I can show my supervisor what we're flagging back at the contractor. Filename waterways_clean.geojson, please. The output should contain only LineString features, each preserving all original columns, with feature_id as the key field. The FeatureCollection must include a top-level dropped_count foreign member (an integer) reporting how many features were removed.
0:00
System info
{
  "adapter": "openrouter",
  "model": "google/gemma-4-26b-a4b-it",
  "base_url": "https://openrouter.ai/api/v1"
}
0:00
Bash ls -F
capetown_waterways.geojson
0:12
Write solve.py
content · 73 lines · py
import geopandas as gpd
import json

def main():
    input_file = 'capetown_waterways.geojson'
    output_file = 'waterways_clean.geojson'
    
    # Load the data
    gdf = gpd.read_file(input_file)
    total_initial = len(gdf)
    
    # Filter criteria:
    # 1. Valid geometry
    # 2. Geometry type must be LineString (or MultiLineString? The prompt says "only LineString features")
    #    Wait, the prompt says: "The output should contain only LineString features". 
    #    Usually, in GIS, if someone says LineString, they might mean any linear geometry, 
    #    but to be safe and literal, I will filter for LineString. 
    #    Actually, let's check the geometry types first.
    
    # Let's check the unique geometry types in the input
    print("Unique geometry types in input:", gdf.geom_type.unique())

    # A feature is "usable" if it has a valid geometry and is a LineString.
    # The prompt says: "Drop any features that cannot represent a valid, usable waterway (a feature needs both a drawable geometry and a proper type to be useful)"
    # And: "The output should contain only LineString features"
    
    # Filtering
    # We need to keep LineString. 
    # Note: MultiLineString is often considered a "line" too, but I will stick to the requirement "only LineString".
    # However, let's see what's in there first.
    
    mask = (gdf.geom_type == 'LineString') & (gdf.is_valid)
    gdf_clean = gdf[mask].copy()
    
    dropped_count = total_initial - len(gdf_clean)
    
    # The requirement: "The FeatureCollection must include a top-level dropped_count foreign member"
    # Standard GeoJSON doesn't support arbitrary top-level members in the FeatureCollection object 
    # that aren't part of the spec, but the prompt explicitly asks for it.
    # We will have to construct the JSON manually to add it.

    # Prepare the GeoJSON structure
    # We need to ensure 'feature_id' is the key field? 
    # "each preserving all original columns, with feature_id as the key field"
    # In a FeatureCollection, 'id' is often used. I'll check if 'feature_id' exists in the columns.
    
    if 'feature_id' not in gdf_clean.columns:
        # If it's not a column, maybe the user means the 'id' property of the feature?
        # But they said "preserving all original columns, with feature_id as the key field".
        # I'll assume it's a column.
        pass

    # Convert cleaned GDF to a dictionary/list of features
    # Using the standard GeoJSON format
    features = []
    for _, row in gdf_clean.iterrows():
        # Create feature dict
        feature = {
            "type": "Feature",
            "id": row.get('feature_id', None), # Trying to use feature_id as the id if possible
            "properties": row.drop('geometry').to_dict(),
            "geometry": geom_to_json(row.geometry)
        }
        features.append(feature)

    # Wait, a better way to do this is to use the geopandas to_json and then manipulate it.
    # Let's try the to_json approach.

    # Let's write a more robust version.

if __name__ == "__main__":
    main()
Wrote solve.py (3097 chars).
0:22
Write solve.py
content · 79 lines · py
import geopandas as gpd
import json
from shapely.geometry import mapping

def main():
    input_file = 'capetown_waterways.geojson'
    output_file = 'waterways_clean.geojson'
    
    # Load the data
    try:
        gdf = gpd.read_file(input_file)
    except Exception as e:
        print(f"Error reading input file: {e}")
        return

    total_initial = len(gdf)
    print(f"Total features in input: {total_initial}")
    print("Unique geometry types in input:", gdf.geom_type.unique())

    # Filter criteria:
    # 1. Valid geometry (is_valid)
    # 2. Only LineString features
    # 3. Geometry is not empty
    
    # Note: The prompt says "The output should contain only LineString features".
    # It doesn't explicitly say "MultiLineString", but in many datasets, waterways are MultiLineStrings.
    # However, following the instruction strictly: "only LineString features".
    
    mask = (gdf.geom_type == 'LineString') & (gdf.is_valid) & (~gdf.geometry.is_empty)
    gdf_clean = gdf[mask].copy()
    
    dropped_count = total_initial - len(gdf_clean)
    print(f"Dropped count: {dropped_count}")
    print(f"Remaining features: {len(gdf_clean)}")

    # Check for 'feature_id' column
    if 'feature_id' not in gdf_clean.columns:
        print("Warning: 'feature_id' column not found in input. Using index instead.")
        gdf_clean['feature_id'] = gdf_clean.index

    # Construct the GeoJSON manually to include the top-level dropped_count
    # A valid GeoJSON FeatureCollection looks like:
    # {
    #   "type": "FeatureCollection",
    #   "features": [...],
    #   "dropped_count": 123
    # }
    
    features = []
    for idx, row in gdf_clean.iterrows():
        # Convert row to dict, excluding geometry
        props = row.drop('geometry').to_dict()
        
        # The prompt says "with feature_id as the key field"
        # This might mean the 'id' field in the Feature object.
        feat_id = row.get('feature_id')
        
        feature = {
            "type": "Feature",
            "id": feat_id,
            "properties": props,
            "geometry": mapping(row.geometry)
        }
        features.append(feature)
        
    output_data = {
        "type": "FeatureCollection",
        "features": features,
        "dropped_count": dropped_count
    }
    
    with open(output_file, 'w') as f:
        json.dump(output_data, f)
    
    print(f"Successfully wrote {output_file}")

if __name__ == "__main__":
    main()
Wrote solve.py (2478 chars).
0:24
Bash python solve.py
Total features in input: 100
Unique geometry types in input: <ArrowStringArray>
[nan, 'LineString']
Length: 2, dtype: str
Dropped count: 15
Remaining features: 85
Successfully wrote waterways_clean.geojson
0:26
Bash # Check the content of the output file to verify the structure and the dropped_c...
# Check the content of the output file to verify the structure and the dropped_count
head -n 20 waterways_clean.geojson
# Also check if dropped_count is at the top level
python -c "import json; data = json.load(open('waterways_clean.geojson')); print('dropped_count:', data.get('dropped_count')); print('type:', data.get('type'))"
{"type": "FeatureCollection", "features": [{"type": "Feature", "id": 16, "properties": {"feature_id": 16, "name": "Steenbras Stream", "waterway_type": NaN}, "geometry": {"type": "LineString", "coordinates": [[18.675131, -34.097358], [18.644434, -34.095261], [18.593333, -34.054287]]}}, {"type": "Feature", "id": 17, "properties": {"feature_id": 17, "name": "Sir Lowry Stream", "waterway_type": NaN}, "geometry": {"type": "LineString", "coordinates": [[18.476831, -34.056422], [18.432213, -34.00126], [18.38696, -33.920017]]}}, {"type": "Feature", "id": 18, "properties": {"feature_id": 18, "name": "Constantia River", "waterway_type": NaN}, "geometry": {"type": "LineString", "coordinates": [[18.350029, -33.922963], [18.357716, -33.862693], [18.38548, -33.811815]]}}, {"type": "Feature", "id": 19, "properties": {"feature_id": 19, "name": "Klipfontein Stream", "waterway_type": NaN}, "geometry": {"type": "LineString", "coordinates": [[18.482936, -33.81301], [18.531519, -33.800031], [18.591089, -33.823751]]}}, {"type": "Feature", "id": 20, "properties": {"feature_id": 20, "name": "Plumstead Stream", "waterway_type": NaN}, "geometry": {"type": "LineString", "coordinates": [[18.678279, -33.822156], [18.695646, -33.867752], [18.698602, -33.945448]]}}, {"type": "Feature", "id": 21, "properties": {"feature_id": 21, "name": NaN, "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.64611, -33.94245], [18.606483, -34.006979], [18.548438, -34.071103]]}}, {"type": "Feature", "id": 22, "properties": {"feature_id": 22, "name": NaN, "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.434177, -34.069308], [18.396375, -34.096669], [18.363484, -34.091471]]}}, {"type": "Feature", "id": 23, "properties": {"feature_id": 23, "name": NaN, "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.357053, -34.092439], [18.377185, -34.058844], [18.418269, -33.988844]]}}, {"type": "Feature", "id": 24, "properties": {"feature_id": 24, "name": NaN, "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.529212, -33.991734], [18.577397, -33.92639], [18.631474, -33.852446]]}}, {"type": "Feature", "id": 25, "properties": {"feature_id": 25, "name": NaN, "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.69512, -33.854745], [18.699836, -33.814463], [18.68664, -33.800862]]}}, {"type": "Feature", "id": 26, "properties": {"feature_id": 26, "name": "Liesbeek Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.608518, -33.800571], [18.562766, -33.820372], [18.501884, -33.878938]]}}, {"type": "Feature", "id": 27, "properties": {"feature_id": 27, "name": "Black River River", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.397951, -33.876311], [18.36964, -33.938979], [18.351439, -34.018796]]}}, {"type": "Feature", "id": 28, "properties": {"feature_id": 28, "name": "Salt Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.375963, -34.016116], [18.407114, -34.067168], [18.458611, -34.098842]]}}, {"type": "Feature", "id": 29, "properties": {"feature_id": 29, "name": "Diep Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.575191, -34.09844], [18.619566, -34.093491], [18.664324, -34.049485]]}}, {"type": "Feature", "id": 30, "properties": {"feature_id": 30, "name": "Eerste River", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.699921, -34.05171], [18.691653, -33.995063], [18.663239, -33.913635]]}}, {"type": "Feature", "id": 31, "properties": {"feature_id": 31, "name": "Lourens Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.565016, -33.916553], [18.516377, -33.857457], [18.456965, -33.809401]]}}, {"type": "Feature", "id": 32, "properties": {"feature_id": 32, "name": "Hout Bay Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.370716, -33.810475], [18.3539, -33.800308], [18.351676, -33.827404]]}}, {"type": "Feature", "id": 33, "properties": {"feature_id": 33, "name": "Disa River", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.405419, -33.8257], [18.445387, -33.873302], [18.503651, -33.951992]]}}, {"type": "Feature", "id": 34, "properties": {"feature_id": 34, "name": "Sand Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.617617, -33.948992], [18.655044, -34.012977], [18.687315, -34.074848]]}}, {"type": "Feature", "id": 35, "properties": {"feature_id": 35, "name": "Soet Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.692343, -34.07316], [18.671677, -34.0979], [18.630054, -34.089161]]}}, {"type": "Feature", "id": 36, "properties": {"feature_id": 36, "name": "Klein Liesbeek River", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.518682, -34.090253], [18.470597, -34.054239], [18.416862, -33.982488]]}}, {"type": "Feature", "id": 37, "properties": {"feature_id": 37, "name": "Kuils Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.354399, -33.98541], [18.350268, -33.919952], [18.364179, -33.847569]]}}, {"type": "Feature", "id": 38, "properties": {"feature_id": 38, "name": "Modder Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.443339, -33.849781], [18.489293, -33.811789], [18.550203, -33.801704]]}}, {"type": "Feature", "id": 39, "properties": {"feature_id": 39, "name": "Princess Vlei River", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.653489, -33.801283], [18.681318, -33.823787], [18.698818, -33.884768]]}}, {"type": "Feature", "id": 40, "properties": {"feature_id": 40, "name": "Riet Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.672923, -33.882079], [18.64132, -33.945514], [18.589435, -34.024544]]}}, {"type": "Feature", "id": 41, "properties": {"feature_id": 41, "name": "Schaapen Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.472795, -34.021926], [18.428668, -34.071142], [18.384411, -34.099512]]}}, {"type": "Feature", "id": 42, "properties": {"feature_id": 42, "name": "Steenbras River", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.350155, -34.09924], [18.359001, -34.091448], [18.388063, -34.044494]]}}, {"type": "Feature", "id": 43, "properties": {"feature_id": 43, "name": "Sir Lowry Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.487037, -34.046805], [18.535726, -33.98878], [18.59497, -33.907323]]}}, {"type": "Feature", "id": 44, "properties": {"feature_id": 44, "name": "Constantia Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.680267, -33.910207], [18.69653, -33.852396], [18.698021, -33.807255]]}}, {"type": "Feature", "id": 45, "properties": {"feature_id": 45, "name": "Klipfontein River", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.643034, -33.808206], [18.602732, -33.800869], [18.544256, -33.83129]]}}, {"type": "Feature", "id": 46, "properties": {"feature_id": 46, "name": "Plumstead Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.430603, -33.82948], [18.393556, -33.878997], [18.36191, -33.958531]]}}, {"type": "Feature", "id": 47, "properties": {"feature_id": 47, "name": "Camps Bay Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.358285, -33.955535], [18.379483, -34.018855], [18.421638, -34.078355]]}}, {"type": "Feature", "id": 48, "properties": {"feature_id": 48, "name": "Bokramspruit River", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.533422, -34.076777], [18.581401, -34.09885], [18.634786, -34.086587]]}}, {"type": "Feature", "id": 49, "properties": {"feature_id": 49, "name": "Mosselbank Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.696058, -34.0878], [18.699603, -34.049435], [18.684979, -33.97607]]}}, {"type": "Feature", "id": 50, "properties": {"feature_id": 50, "name": "Skoolspruit Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.604792, -33.979019], [18.558642, -33.913571], [18.497715, -33.842887]]}}, {"type": "Feature", "id": 51, "properties": {"feature_id": 51, "name": "Vergenoegd River", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.395091, -33.845009], [18.367746, -33.809378], [18.35095, -33.802829]]}}, {"type": "Feature", "id": 52, "properties": {"feature_id": 52, "name": "Liesbeek Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.378214, -33.802278], [18.410262, -33.827442], [18.462528, -33.890721]]}}, {"type": "Feature", "id": 53, "properties": {"feature_id": 53, "name": "Black River Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.579211, -33.887977], [18.623083, -33.952058], [18.666833, -34.03015]]}}, {"type": "Feature", "id": 54, "properties": {"feature_id": 54, "name": "Salt River", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.699743, -34.027599], [18.69032, -34.074885], [18.660616, -34.099897]]}}, {"type": "Feature", "id": 55, "properties": {"feature_id": 55, "name": "Diep Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.560904, -34.099756], [18.512172, -34.089136], [18.453104, -34.039323]]}}, {"type": "Feature", "id": 56, "properties": {"feature_id": 56, "name": "Eerste Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.368772, -34.041715], [18.353065, -33.982423], [18.352308, -33.901092]]}}, {"type": "Feature", "id": 57, "properties": {"feature_id": 57, "name": "Lourens River", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.40853, -33.903937], [18.449161, -33.847521], [18.507838, -33.805381]]}}, {"type": "Feature", "id": 58, "properties": {"feature_id": 58, "name": "Hout Bay Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.621164, -33.806206], [18.657825, -33.801714], [18.688842, -33.835403]]}}, {"type": "Feature", "id": 59, "properties": {"feature_id": 59, "name": "Disa Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.691062, -33.83349], [18.669337, -33.884828], [18.626655, -33.965055]]}}, {"type": "Feature", "id": 60, "properties": {"feature_id": 60, "name": "Sand River", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.514474, -33.962067], [18.466609, -34.024602], [18.413581, -34.081619]]}}, {"type": "Feature", "id": 61, "properties": {"feature_id": 61, "name": "Soet Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.35351, -34.080153], [18.350551, -34.099517], [18.365886, -34.083753]]}}, {"type": "Feature", "id": 62, "properties": {"feature_id": 62, "name": "Klein Liesbeek Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.447088, -34.085084], [18.493427, -34.044442], [18.554364, -33.969602]]}}, {"type": "Feature", "id": 63, "properties": {"feature_id": 63, "name": "Kuils River", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.656311, -33.972572], [18.683167, -33.907259], [18.699257, -33.838409]]}}, {"type": "Feature", "id": 64, "properties": {"feature_id": 64, "name": "Modder Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.670629, -33.840436], [18.638139, -33.807235], [18.5855, -33.804233]]}}, {"type": "Feature", "id": 65, "properties": {"feature_id": 65, "name": "Princess Vlei Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.46879, -33.803555], [18.425179, -33.831331], [18.381943, -33.896787]]}}, {"type": "Feature", "id": 66, "properties": {"feature_id": 66, "name": "Riet River", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.350383, -33.893993], [18.360383, -33.958598], [18.390725, -34.035604]]}}, {"type": "Feature", "id": 67, "properties": {"feature_id": 67, "name": "Schaapen Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.491161, -34.033124], [18.539928, -34.07839], [18.598811, -34.099997]]}}, {"type": "Feature", "id": 68, "properties": {"feature_id": 68, "name": "Steenbras Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.682166, -34.099986], [18.697315, -34.08656], [18.697339, -34.033982]]}}, {"type": "Feature", "id": 69, "properties": {"feature_id": 69, "name": "Sir Lowry River", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.63989, -34.036451], [18.598935, -33.976004], [18.540064, -33.894953]]}}, {"type": "Feature", "id": 70, "properties": {"feature_id": 70, "name": "Constantia Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.427083, -33.897755], [18.390813, -33.842841], [18.360429, -33.803782]]}}, {"type": "Feature", "id": 71, "properties": {"feature_id": 71, "name": "Klipfontein Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.359614, -33.804481], [18.381865, -33.802842], [18.425067, -33.839733]]}}, {"type": "Feature", "id": 72, "properties": {"feature_id": 72, "name": "Plumstead River", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.537628, -33.837721], [18.585372, -33.890782], [18.638035, -33.97155]]}}, {"type": "Feature", "id": 73, "properties": {"feature_id": 73, "name": "Camps Bay Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.696898, -33.968577], [18.699269, -34.030207], [18.683225, -34.084631]]}}, {"type": "Feature", "id": 74, "properties": {"feature_id": 74, "name": "Bokramspruit Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.60102, -34.083282], [18.554499, -34.099899], [18.493562, -34.080664]]}}, {"type": "Feature", "id": 75, "properties": {"feature_id": 75, "name": "Mosselbank River", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.392306, -34.082111], [18.365943, -34.039269], [18.350562, -33.963097]]}}, {"type": "Feature", "id": 76, "properties": {"feature_id": 76, "name": "Skoolspruit Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.38055, -33.966083], [18.413476, -33.901029], [18.466481, -33.834144]]}}, {"type": "Feature", "id": 77, "properties": {"feature_id": 77, "name": "Vergenoegd Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.583201, -33.836072], [18.626544, -33.805364], [18.669259, -33.805916]]}}, {"type": "Feature", "id": 78, "properties": {"feature_id": 78, "name": "Liesbeek River", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.699465, -33.80511], [18.68889, -33.835446], [18.657914, -33.902955]]}}, {"type": "Feature", "id": 79, "properties": {"feature_id": 79, "name": "Black River Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.55677, -33.900116], [18.507975, -33.965121], [18.449284, -34.040895]]}}, {"type": "Feature", "id": 80, "properties": {"feature_id": 80, "name": "Salt Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.366919, -34.03849], [18.35233, -34.081651], [18.353039, -34.099811]]}}, {"type": "Feature", "id": 81, "properties": {"feature_id": 81, "name": "Diep River", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.411707, -34.099932], [18.452979, -34.083723], [18.512036, -34.028481]]}}, {"type": "Feature", "id": 82, "properties": {"feature_id": 82, "name": "Eerste Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.624656, -34.031022], [18.660529, -33.969536], [18.690275, -33.88892]]}}, {"type": "Feature", "id": 83, "properties": {"feature_id": 83, "name": "Lourens Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.689685, -33.891672], [18.666913, -33.838365], [18.623197, -33.802461]]}}, {"type": "Feature", "id": 84, "properties": {"feature_id": 84, "name": "Hout Bay River", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.510272, -33.803032], [18.462655, -33.804249], [18.410365, -33.844273]]}}, {"type": "Feature", "id": 85, "properties": {"feature_id": 85, "name": "Disa Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.35272, -33.842166], [18.350936, -33.896849], [18.367686, -33.978004]]}}, {"type": "Feature", "id": 86, "properties": {"feature_id": 86, "name": "Sand Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.450883, -33.975051], [18.49758, -34.035659], [18.558508, -34.087388]]}}, {"type": "Feature", "id": 87, "properties": {"feature_id": 87, "name": "Soet River", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.659058, -34.086157], [18.684924, -34.099996], [18.699594, -34.077327]]}}, {"type": "Feature", "id": 88, "properties": {"feature_id": 88, "name": "Klein Liesbeek Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.668251, -34.078887], [18.634893, -34.033927], [18.58153, -33.956568]]}}, {"type": "Feature", "id": 89, "properties": {"feature_id": 89, "name": "Kuils Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.464817, -33.959563], [18.421748, -33.894891], [18.379559, -33.830098]]}}, {"type": "Feature", "id": 90, "properties": {"feature_id": 90, "name": "Modder River", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.350713, -33.831925], [18.36186, -33.803767], [18.393466, -33.807872]]}}, {"type": "Feature", "id": 91, "properties": {"feature_id": 91, "name": "Princess Vlei Stream", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.495303, -33.806941], [18.544121, -33.839778], [18.602609, -33.909212]]}}, {"type": "Feature", "id": 92, "properties": {"feature_id": 92, "name": "Riet Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.683973, -33.906333], [18.698, -33.971616], [18.696557, -34.046013]]}}, {"type": "Feature", "id": 93, "properties": {"feature_id": 93, "name": "Schaapen River", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.636679, -34.043689], [18.595096, -34.084661], [18.535863, -34.09934]]}}, {"type": "Feature", "id": 94, "properties": {"feature_id": 94, "name": "Steenbras Stream", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.42362, -34.099592], [18.388148, -34.080632], [18.359045, -34.022831]]}}, {"type": "Feature", "id": 95, "properties": {"feature_id": 95, "name": "Sir Lowry Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.361039, -34.025439], [18.38433, -33.963031], [18.428554, -33.883003]]}}, {"type": "Feature", "id": 96, "properties": {"feature_id": 96, "name": "Constantia River", "waterway_type": "river"}, "geometry": {"type": "LineString", "coordinates": [[18.541825, -33.8857], [18.589308, -33.834101], [18.641218, -33.801421]]}}, {"type": "Feature", "id": 97, "properties": {"feature_id": 97, "name": "Klipfontein Stream", "waterway_type": "drain"}, "geometry": {"type": "LineString", "coordinates": [[18.697637, -33.801863], [18.698834, -33.805934], [18.68138, -33.849015]]}}, {"type": "Feature", "id": 98, "properties": {"feature_id": 98, "name": "Plumstead Stream", "waterway_type": "canal"}, "geometry": {"type": "LineString", "coordinates": [[18.597204, -33.846817], [18.550338, -33.903018], [18.489427, -33.984405]]}}, {"type": "Feature", "id": 99, "properties": {"feature_id": 99, "name": "Camps Bay River", "waterway_type": "ditch"}, "geometry": {"type": "LineString", "coordinates": [[18.389598, -33.981478], [18.364233, -34.040948], [18.350275, -34.089883]]}}, {"type": "Feature", "id": 100, "properties": {"feature_id": 100, "name": "Bokramspruit Stream", "waterway_type": "stream"}, "geometry": {"type": "LineString", "coordinates": [[18.382969, -34.088772], [18.416754, -34.099808], [18.470467, -34.073747]]}}], "dropped_count": 15}dropped_count: 15
type: FeatureCollection
0:28
Bash python -c "import json; data = json.load(open('waterways_clean.geojson')); print...
python -c "import json; data = json.load(open('waterways_clean.geojson')); print(data['features'][0]['properties'])"
{'feature_id': 16, 'name': 'Steenbras Stream', 'waterway_type': nan}
0:42
Write solve.py
content · 71 lines · py
import geopandas as gpd
import json
from shapely.geometry import mapping

def main():
    input_file = 'capetown_waterways.geojson'
    output_file = 'waterways_clean.geojson'
    
    # Load the data
    try:
        gdf = gpd.read_file(input_file)
    except Exception as e:
        print(f"Error reading input file: {e}")
        return

    total_initial = len(gdf)
    print(f"Total features in input: {total_initial}")

    # Filter criteria:
    # 1. Valid geometry (is_valid)
    # 2. Only LineString features
    # 3. Geometry is not empty
    mask = (gdf.geom_type == 'LineString') & (gdf.is_valid) & (~gdf.geometry.is_empty)
    gdf_clean = gdf[mask].copy()
    
    dropped_count = total_initial - len(gdf_clean)
    print(f"Dropped count: {dropped_count}")
    print(f"Remaining features: {len(gdf_clean)}")

    # Ensure 'feature_id' exists and use it for the 'id' field in Feature
    if 'feature_id' not in gdf_clean.columns:
        gdf_clean['feature_id'] = gdf_clean.index

    # Construct the GeoJSON manually to include the top-level dropped_count
    features = []
    for _, row in gdf_clean.iterrows():
        # Convert row to dict, excluding geometry
        props = row.drop('geometry').to_dict()
        
        # Clean up NaN values for JSON serialization
        # JSON does not support NaN, we should convert them to None (null in JSON)
        for key, value in props.items():
            if hasattr(value, '__float__') and value != value: # Check for NaN
                props[key] = None
            elif value is None:
                props[key] = None

        feat_id = row.get('feature_id')
        
        feature = {
            "type": "Feature",
            "id": feat_id,
            "properties": props,
            "geometry": mapping(row.geometry)
        }
        features.append(feature)
        
    output_data = {
        "type": "FeatureCollection",
        "features": features,
        "dropped_count": dropped_count
    }
    
    with open(output_file, 'w') as f:
        json.dump(output_data, f)
    
    print(f"Successfully wrote {output_file}")

if __name__ == "__main__":
    main()
Wrote solve.py (2148 chars).
0:43
Bash python solve.py
Total features in input: 100
Dropped count: 15
Remaining features: 85
Successfully wrote waterways_clean.geojson
0:46
Bash python -c "import json; data = json.load(open('waterways_clean.geojson')); print...
python -c "import json; data = json.load(open('waterways_clean.geojson')); print(data['features'][0]['properties'])"
{'feature_id': 16, 'name': 'Steenbras Stream', 'waterway_type': None}
0:53