dc-l1-tokyo-ring-orientation

Status: done
Score: 1.000
Duration: 2:16
Cost: 0.81¢
Model: google/gemma-4-26b-a4b-it

Map (reference ← swipe → agent)

0:00
tokyo_buildings_legacy.geojson is a footprint export from an old in-house tool. When we load it into web-based GeoJSON viewers, building interiors are shaded incorrectly: holes appear filled and exteriors render as empty space. Our tile-server import script also rejects every feature with geometry warnings. Fix the geometries so that standard GeoJSON web viewers render them correctly. Attributes must be untouched. Output to tokyo_buildings_fixed.geojson. Every feature must remain a Polygon with interior rings and holes preserved exactly as they are; do not flatten them. feature_id is the join key.
0:00
System info
{
  "adapter": "openrouter",
  "model": "google/gemma-4-26b-a4b-it",
  "base_url": "https://openrouter.ai/api/v1"
}
0:01
Bash ls -lh tokyo_buildings_legacy.geojson
-rw-r--r-- 1 agent agent 89K Jun 16 12:11 tokyo_buildings_legacy.geojson
0:04
Bash head -n 20 tokyo_buildings_legacy.geojson
{
  "type": "FeatureCollection",
  "name": "tokyo_buildings_legacy",
  "crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
    }
  },
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              139.7029081,
              35.6592372
            ],
0:10
Write solve.py
content · 52 lines · py
import geopandas as gpd

# Load the data
gdf = gpd.read_file('tokyo_buildings_legacy.geojson')

# Check the first few features' geometries to see if they have holes or inverted rings
print("First 5 geometries:")
for i, geom in enumerate(gdf.geometry.head()):
    print(f"{i}: {geom}")

# In GeoJSON, the exterior ring should be counter-clockwise (for some standards) 
# or follow the Right Hand Rule. 
# However, the most common issue with "inverted" shading in web viewers 
# is the orientation of the rings.
# Standard GeoJSON (RFC 7946) specifies that exterior rings should be counter-clockwise 
# and interior rings clockwise.
# Shapely/GeoPandas usually follows the Right Hand Rule (exterior is CCW).

# Let's try to fix the orientation of the rings.
def fix_orientation(geom):
    if geom.is_empty:
        return geom
    if geom.geom_type == 'Polygon':
        # orient() ensures exterior is CCW and interior rings are CW
        from shapely.geometry import Polygon
        return geom.buffer(0).simplify(0) # Sometimes a buffer(0) fixes self-intersections
    return geom

# Actually, the description says "holes appear filled and exteriors render as empty space".
# This is a classic symptom of inverted ring winding.
# In Shapely, the first ring in coordinates is the exterior.

def fix_ring_winding(polygon):
    if not polygon.interiors:
        # It's a simple polygon. Ensure it's valid.
        return polygon.simplify(0) 
    
    # A more robust way to fix winding in Shapely is to use the fact that
    # orient(polygon, sign=1.0) makes exterior CCW and interior CW.
    from shapely.ops import orient
    return orient(polygon, sign=1.0)

# Let's apply orient to each geometry
gdf['geometry'] = gdf['geometry'].apply(lambda x: fix_ring_winding(x) if x.geom_type == 'Polygon' else x)

# Check if it's still valid
print(f"Invalid geometries after fix: {gdf.geometry.is_valid.value_counts().get(False, 0)}")

# Save the result
gdf.to_file('tokyo_buildings_fixed.geojson', driver='GeoJSON')
print("Saved to tokyo_buildings_fixed.geojson")
Wrote solve.py (2056 chars).
0:12
Bash python solve.py
First 5 geometries:
0: POLYGON ((139.7029081 35.6592372, 139.7027971 35.659202, 139.7027755 35.6592471, 139.7028864 35.6592823, 139.7029081 35.6592372))
1: POLYGON ((139.7010896 35.6601981, 139.7010851 35.660084, 139.7010559 35.660088, 139.7010511 35.6600707, 139.7009769 35.6600834, 139.7009923 35.6601243, 139.7009007 35.6601417, 139.7009195 35.6602237, 139.7010896 35.6601981))
2: POLYGON ((139.7011168 35.6616718, 139.7009949 35.6616685, 139.7009926 35.6617229, 139.7011145 35.6617263, 139.7011168 35.6616718))
3: POLYGON ((139.7032678 35.6601827, 139.7032982 35.660114, 139.7032205 35.6600913, 139.7031901 35.66016, 139.7032678 35.6601827))
4: POLYGON ((139.7015768 35.6592033, 139.701614 35.6592118, 139.7016492 35.6591105, 139.7015748 35.6590934, 139.7015395 35.6591947, 139.7015768 35.6592033))
Invalid geometries after fix: 0
Saved to tokyo_buildings_fixed.geojson
0:17
Write solve.py
content · 44 lines · py
import geopandas as gpd
from shapely.ops import orient

# Load the data
gdf = gpd.read_file('tokyo_buildings_legacy.geojson')

print("Initial validity check:")
print(gdf.geometry.is_valid.value_counts())

def fix_geometry(geom):
    if geom.is_empty:
        return geom
    
    # First, ensure it's valid. buffer(0) is a common trick for fixing self-intersections
    # which might be causing the "geometry warnings" in the tile server.
    if not geom.is_valid:
        geom = geom.buffer(0)
    
    if geom.geom_type == 'Polygon':
        # The prompt says "holes appear filled and exteriors render as empty space".
        # This is exactly what happens when the exterior ring is CW and interior is CCW
        # (or vice versa depending on the renderer's winding rule).
        # RFC 7946 says exterior should be CCW.
        # orient(geom, sign=1.0) makes exterior CCW and interior CW.
        return orient(geom, sign=1.0)
    
    elif geom.geom_type == 'MultiPolygon':
        # For MultiPolygon, we orient each part
        from shapely.geometry import MultiPolygon
        new_parts = [orient(p, sign=1.0) for p in geom.geoms]
        return MultiPolygon(new_parts)
    
    return geom

# Apply fix
gdf['geometry'] = gdf['geometry'].apply(fix_geometry)

print("\nFinal validity check:")
print(gdf.geometry.is_valid.value_counts())

# Ensure we keep all original attributes and save
gdf.to_file('tokyo_buildings_fixed.geojson', driver='GeoJSON')
print("\nSaved to tokyo_buildings_fixed.geojson")
Wrote solve.py (1509 chars).
0:19
Bash python solve.py
Initial validity check:
True    100
Name: count, dtype: int64

Final validity check:
True    100
Name: count, dtype: int64

Saved to tokyo_buildings_fixed.geojson
0:33
Bash # Let's check if there were any invalid geometries in the ORIGINAL file.
# Let's check if there were any invalid geometries in the ORIGINAL file.
# My previous script printed "Initial validity check" but it seems it was already valid?
# Wait, if it's already valid, why the shading issue?
# Ah, "valid" in Shapely means it follows the rules of a valid polygon (no self-intersections),
# but "valid" for GeoJSON/Web-viewers also implies correct winding (Right Hand Rule / RFC 7946).
# If the input was "valid" but the rings were inverted, it would still be "valid" in Shapely terms 
# but render incorrectly.

# Let's verify the content of the output file to ensure it's a valid GeoJSON.
head -n 30 tokyo_buildings_fixed.geojson
{
"type": "FeatureCollection",
"name": "tokyo_buildings_fixed",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "feature_id": 1, "overture_id": "01302ced-3141-4b64-b630-98019352a9fd", "name_primary": "渋谷協和ビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7029081, 35.6592372 ], [ 139.702886400000011, 35.6592823 ], [ 139.7027755, 35.6592471 ], [ 139.7027971, 35.659202 ], [ 139.7029081, 35.6592372 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 2, "overture_id": "046ab130-a9a2-4a0e-9142-b7ea36374fdb", "name_primary": "丸大ビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.701089599999989, 35.6601981 ], [ 139.7009195, 35.6602237 ], [ 139.7009007, 35.6601417 ], [ 139.7009923, 35.6601243 ], [ 139.7009769, 35.6600834 ], [ 139.7010511, 35.6600707 ], [ 139.7010559, 35.660088 ], [ 139.7010851, 35.660084 ], [ 139.701089599999989, 35.6601981 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 3, "overture_id": "063348bb-5559-4839-bf7d-8c6fca154867", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7011168, 35.6616718 ], [ 139.701114499999989, 35.6617263 ], [ 139.7009926, 35.6617229 ], [ 139.700994900000012, 35.6616685 ], [ 139.7011168, 35.6616718 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 4, "overture_id": "0a1085e0-b946-40d4-8b22-17f5110beee6", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7032678, 35.6601827 ], [ 139.7031901, 35.66016 ], [ 139.703220499999986, 35.6600913 ], [ 139.7032982, 35.660114 ], [ 139.7032678, 35.6601827 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 5, "overture_id": "0c2ac2ae-6836-429c-888f-b33dba5cd074", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7015768, 35.6592033 ], [ 139.7015395, 35.6591947 ], [ 139.7015748, 35.6590934 ], [ 139.7016492, 35.6591105 ], [ 139.701614, 35.6592118 ], [ 139.7015768, 35.6592033 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 6, "overture_id": "0ff95784-5c37-4c0e-8ee3-674f96075b8c", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7025726, 35.661211 ], [ 139.7024485, 35.6612117 ], [ 139.7024481, 35.6611726 ], [ 139.7025916, 35.6611717 ], [ 139.7025917, 35.6611836 ], [ 139.7027837, 35.6611824 ], [ 139.702784400000013, 35.6612685 ], [ 139.702573199999989, 35.6612697 ], [ 139.7025726, 35.661211 ] ], [ [ 139.70261625, 35.6612158 ], [ 139.70261625, 35.6612207 ], [ 139.702633065000015, 35.6612207 ], [ 139.702633065000015, 35.6612158 ], [ 139.70261625, 35.6612158 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 7, "overture_id": "11b3ca95-3d26-4876-b2a0-c82b792992ec", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7015979, 35.6600884 ], [ 139.7016004, 35.6601443 ], [ 139.7015731, 35.6601452 ], [ 139.7015706, 35.6600892 ], [ 139.7015979, 35.6600884 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 8, "overture_id": "12f66548-a7b0-491d-b900-32b6cbe7fe79", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.702111800000011, 35.6613063 ], [ 139.702051399999988, 35.6613056 ], [ 139.702046599999989, 35.6612494 ], [ 139.7021249, 35.6612491 ], [ 139.702111800000011, 35.6613063 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 9, "overture_id": "1b3f9098-7a2a-4dfb-b30c-baf5c9fd1822", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.701766, 35.6593673 ], [ 139.701749799999988, 35.6593598 ], [ 139.701775199999986, 35.6593235 ], [ 139.701807699999989, 35.6593385 ], [ 139.7017823, 35.6593748 ], [ 139.701766, 35.6593673 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 10, "overture_id": "1cf6de61-92eb-40c4-be28-513df0dcaf35", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7028858, 35.6592884 ], [ 139.702862399999987, 35.6593419 ], [ 139.702747100000011, 35.6593067 ], [ 139.702771399999989, 35.6592546 ], [ 139.7028858, 35.6592884 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 11, "overture_id": "21367bab-af93-4dca-bf9c-a65708051c5a", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7036803, 35.6585017 ], [ 139.7036139, 35.6584833 ], [ 139.7036697, 35.6583584 ], [ 139.703737100000012, 35.6583662 ], [ 139.7036803, 35.6585017 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 12, "overture_id": "236104c3-84a8-47c1-b872-9860b64a29fa", "name_primary": "サンクスビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7011947, 35.6613077 ], [ 139.70095950000001, 35.6613288 ], [ 139.7009519, 35.6612825 ], [ 139.701190200000013, 35.6612626 ], [ 139.7011947, 35.6613077 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 13, "overture_id": "23f68c5b-71c1-4e47-a535-4a092bd91854", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7001093, 35.6596517 ], [ 139.7000763, 35.6596481 ], [ 139.700078499999989, 35.6596343 ], [ 139.7000808, 35.6596207 ], [ 139.7001138, 35.6596242 ], [ 139.7001093, 35.6596517 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 14, "overture_id": "27112e19-5779-46d8-99d1-1a07c9c1d768", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7009482, 35.6591558 ], [ 139.7008016, 35.6591385 ], [ 139.7008258, 35.6590153 ], [ 139.7009717, 35.6590342 ], [ 139.7009521, 35.6591336 ], [ 139.7009482, 35.6591558 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 15, "overture_id": "28ada99a-21bf-42ff-b6d7-be0a0b26b3e3", "name_primary": "渋谷東日本ビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.700953199999987, 35.6612732 ], [ 139.7009495, 35.6611664 ], [ 139.701184, 35.6611653 ], [ 139.701194, 35.6612552 ], [ 139.700953199999987, 35.6612732 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 16, "overture_id": "29a2845a-cdc5-4aa7-ac7c-0ce136d7e197", "name_primary": "東京スクールオブミュージック専門学校渋谷第2校舎", "building_class": "college", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.703710699999988, 35.6611359 ], [ 139.7036003, 35.661165 ], [ 139.7036077, 35.6611834 ], [ 139.703522, 35.661206 ], [ 139.70347240000001, 35.6610819 ], [ 139.7036503, 35.6610932 ], [ 139.7036895, 35.6610829 ], [ 139.703710699999988, 35.6611359 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 17, "overture_id": "2c1a77ab-b6f8-4a1f-b4ba-b95e3fba63bf", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7033422, 35.6593349 ], [ 139.703213799999986, 35.6595646 ], [ 139.7031249, 35.6595511 ], [ 139.7031901, 35.6593654 ], [ 139.703211, 35.6593243 ], [ 139.7032376, 35.6593313 ], [ 139.7032552, 35.6593028 ], [ 139.7033422, 35.6593349 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 18, "overture_id": "2c40b108-7a5d-4e5a-9275-a464616b6e86", "name_primary": "", "building_class": "", "height": 30.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7028171, 35.6598877 ], [ 139.702736, 35.6598859 ], [ 139.702617, 35.6597873 ], [ 139.7027047, 35.6597141 ], [ 139.702826499999986, 35.6597316 ], [ 139.7028171, 35.6598877 ] ], [ [ 139.70271756, 35.659795692 ], [ 139.70271756, 35.659804372 ], [ 139.702728035, 35.659804372 ], [ 139.702728035, 35.659795692 ], [ 139.70271756, 35.659795692 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 19, "overture_id": "2c4b68ab-0162-4896-9404-f5df5b3479b6", "name_primary": "共栄ビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7037724, 35.6585288 ], [ 139.70369070000001, 35.658506 ], [ 139.7037541, 35.6583544 ], [ 139.70384, 35.6583634 ], [ 139.7037724, 35.6585288 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 20, "overture_id": "2c7a7e5e-8ef8-4734-a812-a1a36e709bf8", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7002922, 35.6618328 ], [ 139.700167099999987, 35.6618375 ], [ 139.700164300000012, 35.6617887 ], [ 139.7002895, 35.661784 ], [ 139.7002922, 35.6618328 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 21, "overture_id": "2e3d3476-bc60-4d92-a59a-4cb2aaeaa3d3", "name_primary": "MAGNET SHIBUYA", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.700983799999989, 35.6601217 ], [ 139.7008993, 35.6601363 ], [ 139.700782800000013, 35.6596766 ], [ 139.700860799999987, 35.6595937 ], [ 139.701050900000013, 35.6595858 ], [ 139.7010502, 35.6597431 ], [ 139.7010765, 35.6600607 ], [ 139.7009683, 35.660077 ], [ 139.700983799999989, 35.6601217 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 22, "overture_id": "2edf3b5a-c9a7-4d67-b57b-e6891fe8263b", "name_primary": "", "building_class": "roof", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.702233400000011, 35.6599842 ], [ 139.7022203, 35.6599837 ], [ 139.702226, 35.6598823 ], [ 139.7022519, 35.6598833 ], [ 139.7022461, 35.6599846 ], [ 139.702233400000011, 35.6599842 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 23, "overture_id": "2f539d97-f723-4927-8208-7bd614daf1e7", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7006873, 35.6609015 ], [ 139.7005119, 35.6609 ], [ 139.7005106, 35.6608351 ], [ 139.700693300000012, 35.6608285 ], [ 139.7006873, 35.6609015 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 24, "overture_id": "348d3001-94c6-42c7-90a3-8e15c681835e", "name_primary": "Zara", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.701175299999989, 35.6607462 ], [ 139.700943200000012, 35.6607253 ], [ 139.7009458, 35.6606009 ], [ 139.7011649, 35.6606144 ], [ 139.701175299999989, 35.6607462 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 25, "overture_id": "371f3842-eea7-4bd0-bf57-c568f5669832", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7002738, 35.6615817 ], [ 139.700080500000013, 35.6616881 ], [ 139.7000434, 35.6616598 ], [ 139.7002095, 35.6615347 ], [ 139.7002654, 35.6615447 ], [ 139.7002738, 35.6615817 ] ] ] } },
1:07
Write solve.py
content · 54 lines · py
import geopandas as gpd
from shapely.ops import orient
from shapely.geometry import Polygon, MultiPolygon

def fix_geometry(geom):
    if geom.is_empty:
        return geom
    
    # Use buffer(0) to fix any structural topological issues first
    if not geom.is_valid:
        geom = geom.buffer(0)
    
    if geom.geom_type == 'Polygon':
        # orient(geom, sign=1.0) ensures exterior is CCW and interior is CW
        # This matches the RFC 7946 GeoJSON standard for the right-hand rule
        return orient(geom, sign=1.0)
    
    elif geom.geom_type == 'MultiPolygon':
        new_parts = []
        for part in geom.geoms:
            if not part.is_empty:
                new_parts.append(orient(part, sign=1.0))
        return MultiPolygon(new_parts)
    
    return geom

def solve():
    input_file = 'tokyo_buildings_legacy.geojson'
    output_file = 'tokyo_buildings_fixed.geojson'
    
    print(f"Loading {input_file}...")
    gdf = gpd.read_file(input_file)
    
    print("Original validity stats:")
    print(gdf.geometry.is_valid.value_counts())
    
    # Apply the fix
    print("Fixing geometries (winding and validity)...")
    gdf['geometry'] = gdf['geometry'].apply(fix_geometry)
    
    print("Final validity stats:")
    print(gdf.geometry.is_valid.value_counts())
    
    # Final check: ensure everything is still a polygon or multipolygon
    # and that the number of features is the same.
    print(f"Total features: {len(gdf)}")
    
    print(f"Saving to {output_file}...")
    gdf.to_file(output_file, driver='GeoJSON')
    print("Done.")

if __name__ == "__main__":
    solve()
Wrote solve.py (1620 chars).
1:10
Bash python solve.py
Loading tokyo_buildings_legacy.geojson...
Original validity stats:
True    100
Name: count, dtype: int64
Fixing geometries (winding and validity)...
Final validity stats:
True    100
Name: count, dtype: int64
Total features: 100
Saving to tokyo_buildings_fixed.geojson...
Done.
1:15
Bash # Compare a feature with a hole in the original vs the fixed
# Compare a feature with a hole in the original vs the fixed
# Feature 18 has a hole.
grep -A 20 '"feature_id": 18' tokyo_buildings_legacy.geojson | head -n 40
grep -A 20 '"feature_id": 18' tokyo_buildings_fixed.geojson | head -n 40
        "feature_id": 18,
        "overture_id": "2c40b108-7a5d-4e5a-9275-a464616b6e86",
        "name_primary": "",
        "building_class": "",
        "height": 30.0
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              139.7037724,
              35.6585288
            ],
            [
              139.70384,
              35.6583634
            ],
{ "type": "Feature", "properties": { "feature_id": 18, "overture_id": "2c40b108-7a5d-4e5a-9275-a464616b6e86", "name_primary": "", "building_class": "", "height": 30.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7028171, 35.6598877 ], [ 139.702736, 35.6598859 ], [ 139.702617, 35.6597873 ], [ 139.7027047, 35.6597141 ], [ 139.702826499999986, 35.6597316 ], [ 139.7028171, 35.6598877 ] ], [ [ 139.70271756, 35.659795692 ], [ 139.70271756, 35.659804372 ], [ 139.702728035, 35.659804372 ], [ 139.702728035, 35.659795692 ], [ 139.70271756, 35.659795692 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 19, "overture_id": "2c4b68ab-0162-4896-9404-f5df5b3479b6", "name_primary": "共栄ビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7037724, 35.6585288 ], [ 139.70369070000001, 35.658506 ], [ 139.7037541, 35.6583544 ], [ 139.70384, 35.6583634 ], [ 139.7037724, 35.6585288 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 20, "overture_id": "2c7a7e5e-8ef8-4734-a812-a1a36e709bf8", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7002922, 35.6618328 ], [ 139.700167099999987, 35.6618375 ], [ 139.700164300000012, 35.6617887 ], [ 139.7002895, 35.661784 ], [ 139.7002922, 35.6618328 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 21, "overture_id": "2e3d3476-bc60-4d92-a59a-4cb2aaeaa3d3", "name_primary": "MAGNET SHIBUYA", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.700983799999989, 35.6601217 ], [ 139.7008993, 35.6601363 ], [ 139.700782800000013, 35.6596766 ], [ 139.700860799999987, 35.6595937 ], [ 139.701050900000013, 35.6595858 ], [ 139.7010502, 35.6597431 ], [ 139.7010765, 35.6600607 ], [ 139.7009683, 35.660077 ], [ 139.700983799999989, 35.6601217 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 22, "overture_id": "2edf3b5a-c9a7-4d67-b57b-e6891fe8263b", "name_primary": "", "building_class": "roof", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.702233400000011, 35.6599842 ], [ 139.7022203, 35.6599837 ], [ 139.702226, 35.6598823 ], [ 139.7022519, 35.6598833 ], [ 139.7022461, 35.6599846 ], [ 139.702233400000011, 35.6599842 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 23, "overture_id": "2f539d97-f723-4927-8208-7bd614daf1e7", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7006873, 35.6609015 ], [ 139.7005119, 35.6609 ], [ 139.7005106, 35.6608351 ], [ 139.700693300000012, 35.6608285 ], [ 139.7006873, 35.6609015 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 24, "overture_id": "348d3001-94c6-42c7-90a3-8e15c681835e", "name_primary": "Zara", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.701175299999989, 35.6607462 ], [ 139.700943200000012, 35.6607253 ], [ 139.7009458, 35.6606009 ], [ 139.7011649, 35.6606144 ], [ 139.701175299999989, 35.6607462 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 25, "overture_id": "371f3842-eea7-4bd0-bf57-c568f5669832", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7002738, 35.6615817 ], [ 139.700080500000013, 35.6616881 ], [ 139.7000434, 35.6616598 ], [ 139.7002095, 35.6615347 ], [ 139.7002654, 35.6615447 ], [ 139.7002738, 35.6615817 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 26, "overture_id": "3c8b6f1b-d6b3-4f03-a4b5-c5508dcaea51", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.703455100000014, 35.660466 ], [ 139.7033433, 35.6604443 ], [ 139.703425399999986, 35.6601648 ], [ 139.7035372, 35.6601865 ], [ 139.703455100000014, 35.660466 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 27, "overture_id": "3ee49086-6ea2-4e7f-b99f-fc2edd7193ed", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.702717799999988, 35.6598821 ], [ 139.7025218, 35.6598754 ], [ 139.7025218, 35.6598616 ], [ 139.7026156, 35.659794 ], [ 139.702717799999988, 35.6598821 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 28, "overture_id": "3f3febaa-698d-497d-be85-c18945288b41", "name_primary": "", "building_class": "roof", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7024615, 35.6600032 ], [ 139.7024489, 35.6600028 ], [ 139.7024539, 35.6599073 ], [ 139.702481299999988, 35.6599082 ], [ 139.7024763, 35.6600037 ], [ 139.7024615, 35.6600032 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 29, "overture_id": "3f9e7083-4ec2-4669-bc45-7ae9fde983a4", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7015039, 35.6595559 ], [ 139.7015049, 35.6596361 ], [ 139.701505700000013, 35.6596938 ], [ 139.701507, 35.6597962 ], [ 139.7014692, 35.6597965 ], [ 139.701467799999989, 35.6596898 ], [ 139.701467400000013, 35.6596561 ], [ 139.7014661, 35.6595562 ], [ 139.7015039, 35.6595559 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 30, "overture_id": "3ff6f135-53ff-48d7-9be7-611b5a1926e1", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7026526, 35.6602298 ], [ 139.70249290000001, 35.6602205 ], [ 139.7024954, 35.6601476 ], [ 139.7026592, 35.6601536 ], [ 139.7026526, 35.6602298 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 31, "overture_id": "41e04ebc-5628-4614-983d-7d2bc049d0cd", "name_primary": "全国婦人会館・ちふれビル", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7034562, 35.6610918 ], [ 139.703512100000012, 35.6612091 ], [ 139.7033032, 35.6612748 ], [ 139.7032656, 35.6612751 ], [ 139.7032652, 35.6612298 ], [ 139.703280400000011, 35.6612297 ], [ 139.7032801, 35.6611929 ], [ 139.703305699999987, 35.6611928 ], [ 139.703304800000012, 35.6610927 ], [ 139.7034562, 35.6610918 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 32, "overture_id": "42569a2d-bc9c-47cf-b338-63e06079d81b", "name_primary": "宮益不動尊", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7035951, 35.6602969 ], [ 139.7035425, 35.6602858 ], [ 139.703554200000013, 35.6602492 ], [ 139.703606799999989, 35.6602603 ], [ 139.7035951, 35.6602969 ] ], [ [ 139.703573364, 35.660271142 ], [ 139.703573364, 35.660273527 ], [ 139.703576578999986, 35.660273527 ], [ 139.703576578999986, 35.660271142 ], [ 139.703573364, 35.660271142 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 33, "overture_id": "42a04eb1-81e4-432c-aba7-f701afb37e1b", "name_primary": "Doctor's Building", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7021437, 35.6608561 ], [ 139.70195240000001, 35.6608552 ], [ 139.701938399999989, 35.6608079 ], [ 139.7021431, 35.6608087 ], [ 139.7021437, 35.6608561 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 34, "overture_id": "45eee2cf-762c-4069-ad33-15ffc6a532fd", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.70351, 35.6583273 ], [ 139.703433499999988, 35.6584926 ], [ 139.7034042, 35.6584846 ], [ 139.7034751, 35.658326 ], [ 139.70351, 35.6583273 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 35, "overture_id": "4615c1ba-4f89-423b-8715-4f89303192c9", "name_primary": "PMO渋谷", "building_class": "office", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7035291, 35.6614183 ], [ 139.703483299999988, 35.6613241 ], [ 139.7034247, 35.6613429 ], [ 139.7033898, 35.6612712 ], [ 139.703615899999988, 35.6611986 ], [ 139.7036966, 35.6613646 ], [ 139.7035291, 35.6614183 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 36, "overture_id": "4642244a-76bc-4f73-9b50-9621e554966b", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.70379650000001, 35.6598824 ], [ 139.703649, 35.6601406 ], [ 139.703585299999986, 35.6601161 ], [ 139.7037276, 35.6598679 ], [ 139.70379650000001, 35.6598824 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 37, "overture_id": "4a232708-e5bc-48da-bfba-70db0a054f1f", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.703694, 35.6586863 ], [ 139.703545600000012, 35.6586423 ], [ 139.703611099999989, 35.6584963 ], [ 139.7037595, 35.6585402 ], [ 139.703694, 35.6586863 ] ] ] } },
{ "type": "Feature", "properties": { "feature_id": 38, "overture_id": "4a869c37-9dae-4491-b2da-57ebd037fa34", "name_primary": "", "building_class": "", "height": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.7029398, 35.660622 ], [ 139.702872500000012, 35.6606192 ], [ 139.7028827, 35.6604838 ], [ 139.70299940000001, 35.66049 ], [ 139.702975400000014, 35.6606254 ], [ 139.702959099999987, 35.6607108 ], [ 139.702923700000014, 35.6607071 ], [ 139.7029398, 35.660622 ] ] ] } },
1:39
Bash # Let's look at the coordinates of the exterior vs interior for feature 18 in th...
# Let's look at the coordinates of the exterior vs interior for feature 18 in the original file.
# I'll use python to get the exact ring directions from the original file.

cat << 'EOF' > check_winding.py
import json
from shapely.geometry import shape

with open('tokyo_buildings_legacy.geojson') as f:
    data = json.load(f)

for feat in data['features']:
    if feat['properties']['feature_id'] == 18:
        geom = shape(feat['geometry'])
        print("Feature 18 (Original):")
        print(f"Exterior: {geom.exterior.is_ccw}")
        for i, interior in enumerate(geom.interiors):
            print(f"Interior {i}: {interior.is_ccw}")
        break

for feat in data['features']:
    if feat['properties']['feature_id'] == 18:
        # Re-reading from fixed file to be sure
        pass

EOF

# Wait, I need to read the fixed file too.
cat << 'EOF' > check_winding.py
import json
from shapely.geometry import shape

def check_file(filename):
    with open(filename) as f:
        data = json.load(f)
    for feat in data['features']:
        if feat['properties']['feature_id'] == 18:
            geom = shape(feat['geometry'])
            print(f"{filename} - Feature 18:")
            print(f"  Exterior: {geom.exterior.is_ccw}")
            for i, interior in enumerate(geom.interiors):
                print(f"  Interior {i}: {interior.is_ccw}")
            return

check_file('tokyo_buildings_legacy.geojson')
check_file('tokyo_buildings_fixed.geojson')
EOF

python check_winding.py
tokyo_buildings_legacy.geojson - Feature 18:
  Exterior: False
  Interior 0: True
tokyo_buildings_fixed.geojson - Feature 18:
  Exterior: True
  Interior 0: False
2:14