crs-l2-fiji-antimeridian

Status: done
Score: 1.000
Duration: 6:14
Cost: 1.36¢
Model: deepseek/deepseek-v4-flash

Map (reference ← swipe → agent)

0:00
I'm pulling together per-transect coverage figures for our survey report, but some of the lines in fiji_transects_wgs84.geojson render as arcs spanning the entire globe instead of short local segments. Can you fix the geometries so they render correctly, convert them to Fiji's national metric grid, and give me each transect as one MultiLineString feature with a length_m attribute in metres? Every feature must come out as a MultiLineString, so do not leave any transects as plain LineString, and the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path. Carry the survey attributes (vessel and survey_date) through unchanged, use transect_id as the key field, and write the result as GeoJSON named fiji_transects_fmg.geojson.
0:00
System info
{
  "adapter": "openrouter",
  "model": "deepseek/deepseek-v4-flash",
  "base_url": "https://openrouter.ai/api/v1"
}
0:07
Let me start by understanding the problem. The user has a GeoJSON file with transect lines in WGS84 (EPSG:4326). Some lines render as arcs spanning the entire globe, which suggests they might have longitudes that wrap around the 180° meridian (the International Date Line passes near Fiji). This is a common issue when coordinates cross the antimeridian - they need to be split into multi-part geometries.

The task is:
1. Read fiji_transects_wgs84.geojson
2. Fix geometries that span the globe (likely antimeridian crossing issues)
3. Convert to Fiji's national metric grid (EPSG:3460 - Fiji 1986 / Fiji Map Grid)
4. Each transect should be a MultiLineString feature
5. Calculate length in metres
6. Write to fiji_transects_fmg.geojson

Let me start by examining the input file.
0:07
Bash ls -la /work/fiji_transects_wgs84.geojson
-rw-r--r-- 1 agent agent 14090 Jun 16 21:49 /work/fiji_transects_wgs84.geojson
0:10
Let me first read the file to understand its structure.
0:10
Read /work/fiji_transects_wgs84.geojson
     1	{
     2	"type": "FeatureCollection",
     3	"name": "fiji_transects_wgs84",
     4	"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
     5	"features": [
     6	{ "type": "Feature", "properties": { "transect_id": "T001", "vessel": "Taveuni II", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.941879580782086, -17.632301816020771 ], [ 179.456655009654696, -17.623768593111961 ], [ 179.971430438527307, -17.611072392823225 ], [ -179.513794132600054, -17.596198176111951 ], [ -178.999018703727444, -17.579632069513806 ], [ -178.484243274854833, -17.577321872344196 ] ] } },
     7	{ "type": "Feature", "properties": { "transect_id": "T002", "vessel": "Bligh", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 179.070288824800542, -17.334767984150709 ], [ 179.725942881740252, -17.171343988196945 ], [ -179.618403061320009, -17.007726843860763 ], [ -178.962749004380299, -16.834850408534852 ] ] } },
     8	{ "type": "Feature", "properties": { "transect_id": "T003", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.636947428758333, -17.358837040696127 ], [ 178.462815605623859, -17.416074837434927 ], [ 179.288683782489386, -17.471713663377283 ], [ -179.885448040645059, -17.527675804260884 ], [ -179.059579863779533, -17.590577543375694 ] ] } },
     9	{ "type": "Feature", "properties": { "transect_id": "T004", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.873696381462679, -17.803295238757038 ], [ 179.542254331047587, -17.598477321703708 ], [ -179.789187719367533, -17.405549472537043 ], [ -179.120629769782624, -17.19411411231702 ], [ -178.452071820197745, -16.992332922174427 ], [ -177.783513870612836, -16.795171196845544 ] ] } },
    10	{ "type": "Feature", "properties": { "transect_id": "T005", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.596924830927293, -17.923255155112717 ], [ 178.160696712947697, -18.010328270600898 ], [ 178.724468594968101, -18.099224972140938 ], [ 179.288240476988506, -18.17532145144995 ], [ 179.85201235900891, -18.270380145331412 ], [ -179.584215758970686, -18.358875431719703 ], [ -179.020443876950281, -18.437390943572723 ] ] } },
    11	{ "type": "Feature", "properties": { "transect_id": "T006", "vessel": "Cakaulevu", "survey_date": "2025-08-13", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.622166647099931, -18.382052393687179 ], [ 179.205668025282989, -18.184610858038685 ], [ 179.789169403466019, -17.991866703295319 ], [ -179.627329218350923, -17.789532271264445 ], [ -179.043827840167864, -17.595682751178369 ], [ -178.460326461984835, -17.393483000040479 ], [ -177.876825083801776, -17.203490564162109 ] ] } },
    12	{ "type": "Feature", "properties": { "transect_id": "T007", "vessel": "Vanua I", "survey_date": "2025-08-19", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.920101619592742, -17.14979850360189 ], [ 178.697177550619955, -17.100522188212071 ], [ 179.474253481647168, -17.053867486482115 ], [ -179.74867058732562, -17.004197778897627 ], [ -178.971594656298407, -16.959481244370728 ], [ -178.194518725271195, -16.911697858015476 ] ] } },
    13	{ "type": "Feature", "properties": { "transect_id": "T008", "vessel": "Taveuni II", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 179.401134548056262, -17.934415957637167 ], [ 179.67454249485786, -17.899654147497863 ], [ 179.947950441659486, -17.858130727687527 ], [ -179.778641611538916, -17.811143619533269 ], [ -179.50523366473729, -17.768768396969666 ], [ -179.231825717935692, -17.724708363613253 ] ] } },
    14	{ "type": "Feature", "properties": { "transect_id": "T009", "vessel": "Vanua I", "survey_date": "2025-08-13", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.068433112639013, -16.657838811131668 ], [ 178.561564721122721, -16.689731320158675 ], [ 179.054696329606429, -16.72173241552732 ], [ 179.547827938090137, -16.748891897767297 ], [ -179.959040453426155, -16.775241845025921 ], [ -179.465908844942447, -16.799281899607834 ] ] } },
    15	{ "type": "Feature", "properties": { "transect_id": "T010", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.602847396853463, -17.277026295424367 ], [ 179.128053569323441, -17.468285296635063 ], [ -179.346740258206609, -17.65419136085664 ], [ -177.82153408573663, -17.837325774502055 ] ] } },
    16	{ "type": "Feature", "properties": { "transect_id": "T011", "vessel": "Taveuni II", "survey_date": "2025-08-20", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.863797870333627, -17.7932433540029 ], [ -176.985724572324699, -17.81273654528005 ], [ -177.107651274315742, -17.824236921378098 ], [ -177.229577976306814, -17.835861250474697 ], [ -177.351504678297886, -17.857559546831705 ], [ -177.473431380288929, -17.871769203842408 ], [ -177.595358082280001, -17.890225470404214 ] ] } },
    17	{ "type": "Feature", "properties": { "transect_id": "T012", "vessel": "Taveuni II", "survey_date": "2025-08-20", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.312195951017685, -17.976122209608448 ], [ 176.362236324041447, -17.722210270969462 ], [ 176.412276697065209, -17.470306839752467 ], [ 176.462317070088972, -17.226475769084448 ] ] } },
    18	{ "type": "Feature", "properties": { "transect_id": "T013", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.70823848972006, -17.696306145080879 ], [ -177.205026853648661, -17.960082567744127 ], [ -177.701815217577291, -18.210737758337459 ], [ -178.198603581505893, -18.469124252379427 ] ] } },
    19	{ "type": "Feature", "properties": { "transect_id": "T014", "vessel": "Lomaiviti", "survey_date": "2025-08-13", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.538496893308832, -16.594358164012139 ], [ 176.861572490827996, -16.71494251806207 ], [ 177.184648088347188, -16.832251271240672 ], [ 177.507723685866381, -16.976116506165042 ], [ 177.830799283385545, -17.090939089364483 ] ] } },
    20	{ "type": "Feature", "properties": { "transect_id": "T015", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.278377449219306, -16.724372731501912 ], [ -177.470251917030794, -16.761535677586746 ], [ -177.662126384842253, -16.787182414326686 ], [ -177.854000852653712, -16.818799270133983 ], [ -178.0458753204652, -16.856282470705128 ], [ -178.237749788276659, -16.891308864567598 ], [ -178.429624256088147, -16.914729063096839 ] ] } },
    21	{ "type": "Feature", "properties": { "transect_id": "T016", "vessel": "Taveuni II", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.445325115686984, -18.225562645051426 ], [ 178.570919872630384, -18.22187730118792 ], [ 178.696514629573755, -18.228085980840198 ], [ 178.822109386517127, -18.243164774607269 ], [ 178.947704143460527, -18.232257588046291 ] ] } },
    22	{ "type": "Feature", "properties": { "transect_id": "T017", "vessel": "Taveuni II", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.949497668404376, -16.593233226112279 ], [ -177.073130399044231, -16.796136633276152 ], [ -177.196763129684086, -16.985064764093782 ], [ -177.320395860323913, -17.188172387851619 ], [ -177.444028590963768, -17.380278567766066 ], [ -177.567661321603623, -17.571298263401527 ], [ -177.691294052243478, -17.76827441812642 ] ] } },
    23	{ "type": "Feature", "properties": { "transect_id": "T018", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.111138114901024, -16.851117503263293 ], [ 176.291399614433374, -17.034309766495799 ], [ 176.471661113965695, -17.221302006070296 ], [ 176.651922613498016, -17.402317112686557 ], [ 176.832184113030365, -17.592994204367653 ] ] } },
    24	{ "type": "Feature", "properties": { "transect_id": "T019", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.007516628261584, -17.706954766350957 ], [ 177.596962177324428, -17.607161226082525 ], [ 177.186407726387301, -17.511279680346377 ], [ 176.775853275450146, -17.418568907300433 ], [ 176.36529882451299, -17.326891370330944 ] ] } },
    25	{ "type": "Feature", "properties": { "transect_id": "T020", "vessel": "Taveuni II", "survey_date": "2025-08-13", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.869640838143511, -17.085093978662378 ], [ 177.34023164697706, -17.269542693344249 ], [ 177.810822455810609, -17.442447410976666 ], [ 178.281413264644158, -17.624621823987695 ], [ 178.752004073477707, -17.807162082048386 ] ] } },
    26	{ "type": "Feature", "properties": { "transect_id": "T021", "vessel": "Vanua I", "survey_date": "2025-08-16", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.258661225850091, -17.092877710432774 ], [ 176.830345357965598, -17.521807453385343 ], [ 177.402029490081105, -17.943438299004075 ], [ 177.973713622196613, -18.366984215963161 ] ] } },
    27	{ "type": "Feature", "properties": { "transect_id": "T022", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 177.115699442929383, -17.820293640195572 ], [ 177.180354072206086, -17.755581112632651 ], [ 177.245008701482789, -17.684692899482695 ], [ 177.30966333075952, -17.61995755685173 ], [ 177.374317960036223, -17.544174647257226 ], [ 177.438972589312925, -17.479955835030527 ] ] } },
    28	{ "type": "Feature", "properties": { "transect_id": "T023", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.191351883954098, -17.679938619650844 ], [ -177.334313691261116, -17.740394590021616 ], [ -177.477275498568133, -17.794367957957562 ], [ -177.62023730587515, -17.835119945356809 ], [ -177.763199113182168, -17.904282380225705 ], [ -177.906160920489185, -17.955635131096855 ] ] } },
    29	{ "type": "Feature", "properties": { "transect_id": "T024", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -179.108274205314615, -16.84950325482971 ], [ -178.651703763804733, -16.962456436762313 ], [ -178.195133322294822, -17.077410225872036 ], [ -177.73856288078494, -17.187966865781235 ], [ -177.281992439275029, -17.288042922581088 ], [ -176.825421997765147, -17.40904519070677 ] ] } },
    30	{ "type": "Feature", "properties": { "transect_id": "T025", "vessel": "Taveuni II", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.574749815324452, -18.225329195672629 ], [ 176.575711406506713, -17.903825702399828 ], [ 176.576672997688945, -17.572376190438085 ], [ 176.577634588871206, -17.245872893430239 ] ] } },
    31	{ "type": "Feature", "properties": { "transect_id": "T026", "vessel": "Cakaulevu", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.630248956242411, -17.547785158041279 ], [ 178.44215451378426, -17.570057172060459 ], [ 178.254060071326109, -17.598148620651486 ], [ 178.065965628867986, -17.611819625668215 ], [ 177.877871186409834, -17.645164465599024 ], [ 177.689776743951683, -17.659546383089758 ] ] } },
    32	{ "type": "Feature", "properties": { "transect_id": "T027", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.773526218774975, -17.183972526301677 ], [ -177.993798674497214, -17.290101550005915 ], [ -178.214071130219452, -17.383420029016879 ], [ -178.434343585941718, -17.487055860389813 ], [ -178.654616041663957, -17.584325384194859 ], [ -178.874888497386195, -17.694189986558115 ] ] } },
    33	{ "type": "Feature", "properties": { "transect_id": "T028", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -178.154921880223981, -16.519500569812283 ], [ -177.760867938660567, -16.61473611015824 ], [ -177.366813997097154, -16.706948361526067 ], [ -176.972760055533769, -16.779276042892469 ], [ -176.578706113970355, -16.874986722588798 ] ] } },
    34	{ "type": "Feature", "properties": { "transect_id": "T029", "vessel": "Lomaiviti", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.563841265411412, -18.091246216590591 ], [ -176.943316553982299, -17.796168551154441 ], [ -177.322791842553215, -17.510273970538908 ], [ -177.702267131124103, -17.231705179840109 ], [ -178.08174241969499, -16.949176694851047 ] ] } },
    35	{ "type": "Feature", "properties": { "transect_id": "T030", "vessel": "Cakaulevu", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.772700226865993, -16.898671712097919 ], [ -177.981739053528088, -17.042373280579991 ], [ -178.190777880190183, -17.175821795383495 ], [ -178.399816706852278, -17.32978967298839 ], [ -178.608855533514344, -17.474383681681747 ], [ -178.817894360176439, -17.613581860280195 ], [ -179.026933186838534, -17.755873350663471 ] ] } }
    36	]
    37	}
0:30
Let me analyze the problem. The user says that some lines "render as arcs spanning the entire globe instead of short local segments." Looking at the data, the problem is with features that cross the antimeridian (180° longitude). When coordinates go from positive (~179.9) to negative (~-179.9) longitude, straight-line rendering interprets this as going all the way around the world instead of crossing the dateline locally.

For example, T001 has coordinates like:
```
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-178.99, -17.58], [-178.48, -17.58]
```

The jump from 179.97 to -179.51 should be a short hop of about 0.5 degrees, but a naive renderer sees it as a jump of ~359.5 degrees the other way.

The fix: for coordinates crossing the antimeridian, we need to split the linestring at the anti-meridian (180°) into multiple segments. For segments that cross from +lon to -lon (or vice versa), we should adjust the longitudes to keep continuity. More specifically, we can add 360° to the negative longitudes in the part that's "east" of 180°, so the coordinates are continuous.

Actually, the better approach for proper rendering and conversion to a local metric grid (Fiji's national metric grid) is:

1. For each linestring, determine if it crosses the antimeridian.
2. If it does, we need to shift coordinates. The common approach is to:
   - For the segments on one side of the antimeridian, add/subtract 360 to longitude so that the path is continuous.
   - Then split the path into separate linestrings where it crosses the antimeridian.
   - Each part will have coordinates in a continuous range.
3. Then we can convert to the projected coordinate system for Fiji.

Fiji's national metric grid... let me think. Fiji uses a variety of projections. Common ones are:
- Fiji Map Grid (FMG) - this is likely what's asked for.
- FMG uses the WGS 84 datum with a Transverse Mercator projection. The central meridian for Fiji is typically 178°E (for the Fiji 1986 datum) or 177°E? Actually, let me look up:
  - Fiji Map Grid (FMG) uses Transverse Mercator, central meridian 178°E, scale factor 0.99985, false easting 500000, false northing 0.
  - Well, there are actually two zones for Fiji. But the common one for the whole country is EPSG:3139 (Fiji 1956 / Fiji Central Lambert) or EPSG:3460 (Fiji 1956 / UTM zone 60S). 
  
Wait, let me search for "Fiji Map Grid" EPSG code. Actually, "Fiji Map Grid" might refer to EPSG:3139... No, 3139 is "Fiji 1956 / Fiji Central Lambert".

Actually, Fiji uses:
- EPSG:3139 - Fiji 1956 / Fiji Central Lambert
- EPSG:3460 - Fiji 1956 / UTM zone 60S
- EPSG:3461 - Fiji 1956 / UTM zone 1S

But the "Fiji Metric Grid" or "Fiji Map Grid" (FMG) - let me check what pyproj knows about this.

Actually, I think the most commonly used projection for Fiji nationwide is the Fiji Map Grid (FMG) which is based on the WGS84 datum with a Transverse Mercator projection. Let me check known EPSG codes.

Actually let me just check what's available in the PROJ database. Let me search for Fiji-related CRS.

Actually, looking more carefully, the "Fiji Map Grid" (FMG) specifically is EPSG:3139... no wait.

Let me search for this. I'll check proj databases.

Let me think about the approach:

1. Read the GeoJSON data with geopandas
2. For features that cross the antimeridian (the `crosses_antimeridian_flag` is true), split them at the antimeridian:
   - Identify segments where longitude jumps from > 170 to < -170 or vice versa
   - Split at the antimeridian, creating two or more LineStrings
3. Since we need MultiLineString output, even single-line features should be wrapped as MultiLineString
4. Convert to Fiji Map Grid projection
5. Calculate length in meters
6. Write as GeoJSON

Let me write a Python script to do this.

For the antimeridian handling, here's my approach:
- For each linestring, iterate through coordinates
- When a segment crosses the antimeridian (from ~+179 to ~-179 or vice versa), we need to split
- We can use shapely's `normalize` and segmentize operations, or manually split

Let me think about a simpler approach:
1. For each feature, check if it crosses the antimeridian
2. If it does, shift all longitudes to a continuous range by adding 360 to negative longitudes
3. Then use the shapely `intersection` with a bounding box that covers the Fiji region to split into parts
4. Or manually find the crossing point and split

Actually, a cleaner approach:
- For coordinates that cross the antimeridian, we can:
  1. Create a copy of the coordinates where we add 360 to all longitudes that are < 0 (for the eastern side)
  2. Then split at the antimeridian (±180°) 

Let me think more carefully. For T001:
Original: [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-178.99, -17.58], [-178.48, -17.58]

If we add 360 to negative longitudes:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [180.49, -17.60], [181.01, -17.58], [181.52, -17.58]

This is a continuous path going from 178.94 to 181.52. Now we need to split it at 180°.

The first part goes from 178.94 to 180.0 (intersection point), then the second goes from 180.0 to 181.52.

But when we project to a local grid, we need to be careful. The FMG projection would handle this properly since it wraps around.

Actually, for the projected coordinate system, I think we can:
1. First handle the antimeridian by splitting the geometries
2. Then convert to the local projection
3. Each part becomes a LineString in a MultiLineString

Let me write the script. First, let me find the right CRS for Fiji Map Grid.

I'll use pyproj to check available CRS.
0:30
Bash python3 -c "import pyproj; crs = pyproj.CRS('EPSG:3460'); print(crs)" 2>/dev/nul...
python3 -c "import pyproj; crs = pyproj.CRS('EPSG:3460'); print(crs)" 2>/dev/null || echo "Not found"
python3 -c "import pyproj; crs = pyproj.CRS('EPSG:3139'); print(crs)" 2>/dev/null || echo "Not found"
python3 -c "import pyproj; crs = pyproj.CRS('EPSG:3461'); print(crs)" 2>/dev/null || echo "Not found"
EPSG:3460
EPSG:3139
EPSG:3461
0:39
Let me find out what Fiji's national metric grid is. The most common Fiji grid is EPSG:3460 (Fiji 1956 / Fiji Central or Fiji Map Grid). Let me check the details.

Actually, let me look up "Fiji Map Grid" - EPSG:3460 seems to be "Fiji 1956 / UTM zone 60S". But Fiji uses the "Fiji Map Grid" which might be different. Let me check.

Actually, the standard Fiji national grid is based on EPSG:3460 (Fiji 1956 / UTM zone 60S) or possibly EPSG:3139. Let me find the right one.

Let me search for what EPSG code is commonly used for Fiji's metric grid.
0:39
Bash python3 -c "
python3 -c "
import pyproj
crs = pyproj.CRS('EPSG:3460')
print('EPSG:3460:', crs)
print()
crs = pyproj.CRS('EPSG:3139')
print('EPSG:3139:', crs)
print()
crs = pyproj.CRS('EPSG:15021')
print('EPSG:15021:', crs)
"
EPSG:3460: EPSG:3460

EPSG:3139: EPSG:3139
Traceback (most recent call last):
  File "<string>", line 9, in <module>
    crs = pyproj.CRS('EPSG:15021')
  File "/usr/local/lib/python3.14/site-packages/pyproj/crs/crs.py", line 350, in __init__
    self._local.crs = _CRS(self.srs)
                      ~~~~^^^^^^^^^^
  File "pyproj/_crs.pyx", line 2364, in pyproj._crs._CRS.__init__
pyproj.exceptions.CRSError: Invalid projection: EPSG:15021: (Internal Proj Error: proj_create: crs not found: EPSG:15021)
0:48
Let me research what projection is Fiji's national metric grid. Fiji typically uses a Transverse Mercator projection. Let me check what's commonly used.

Common Fiji projected coordinate systems:
- EPSG:3460 - Fiji 1956 / UTM zone 60S
- EPSG:3139 - WGS 84 / UTM zone 1S (Wait, that doesn't seem right)
- EPSG:3461 - Fiji 1956 / UTM zone 60S

Actually, Fiji spans UTM zones 1 (180°W to 174°W), 60 (174°E to 180°E). Let me check more carefully.

The main Fiji map grid is likely "Fiji 1986 / Fiji Map Grid" which is EPSG:3460.

Let me look at what's appropriate. Actually, for Fiji, the national grid is typically "Fiji Map Grid" which uses EPSG:3460 (Fiji 1956 / Fiji Map Grid) or EPSG:3139 (Fiji 1986 / Fiji Map Grid) or similar.

Wait, let me check what's commonly known as Fiji's metric grid. Fiji Map Grid (FMG) is EPSG:3139 or EPSG:3460? Let me look more carefully.

Let me check - EPSG:3139 is "Fiji 1986 / Fiji Map Grid" - this is likely the one.

But actually, let me check the EPSG database more carefully.
0:48
Bash python3 -c "
python3 -c "
import pyproj
# Check several Fiji-related CRS
for code in ['EPSG:3460', 'EPSG:3139', 'EPSG:3461', 'EPSG:3138']:
    try:
        crs = pyproj.CRS(code)
        print(f'{code}: {crs.name} | {crs.area_of_use}')
    except:
        print(f'{code}: not found')
"
EPSG:3460: Fiji 1986 / Fiji Map Grid | - name: Fiji - onshore. Includes Viti Levu, Vanua Levu, Taveuni, the Yasawa Group, the Kadavu Group, the Lau Islands and Rotuma Islands.
- bounds: (176.81, -20.81, -178.15, -12.42)
EPSG:3139: Vanua Levu 1915 / Vanua Levu Grid | - name: Fiji - Vanua Levu and Taveuni.
- bounds: (178.42, -17.07, -179.77, -16.1)
EPSG:3461: Dabola 1981 / UTM zone 28N | - name: Guinea - onshore west of 12°W.
- bounds: (-15.13, 9.01, -12.0, 12.68)
EPSG:3138: ETRS89 / ETRS-GK31FIN | - name: Finland - east of 30°30'E.
- bounds: (30.5, 62.08, 31.59, 64.27)
1:16
EPSG:3460 (Fiji 1986 / Fiji Map Grid) is the one that covers all of Fiji, including the onshore areas. That's the Fiji national metric grid. Let me check more about it.

The key problem: some transects span across the antimeridian (180° longitude). In GeoJSON with WGS84 coordinates, longitudes near 180° and -180° can cause rendering engines to draw lines all the way around the globe. The solution is to split lines that cross the antimeridian into segments on each side, so the geometries render correctly.

Let me think about this problem:

1. Features where `crosses_antimeridian_flag` is true have coordinates that cross the antimeridian (e.g., [179.xxx, ...] to [-179.xxx, ...]).
2. These need to be split into multiple segments - one for the east-of-180 side (positive longitudes) and one for the west-of-180 side (negative longitudes).
3. Each feature should become a MultiLineString with one or more component LineStrings.
4. Then convert to EPSG:3460 (Fiji Map Grid), compute lengths in meters, and output as GeoJSON.

Let me look at the data more carefully:

For T001: coordinates go from 178.94, 179.45, 179.97, then -179.51, -178.99, -178.48
- So points 1-3 are east of 180 (lon > 0)
- Points 4-6 are west of 180 (lon < 0)
- This needs to be split into two LineStrings

For T002: 179.07, 179.72, then -179.61, -178.96
- Points 1-2 east, points 3-4 west

For T003: 177.63, 178.46, 179.28, then -179.88, -179.05
- Points 1-3 east, points 4-5 west

For T004: 178.87, 179.54, then -179.78, -179.12, -178.45, -177.78
- Points 1-2 east, points 3-6 west

For T005: 177.59, 178.16, 178.72, 179.28, 179.85, then -179.58, -179.02
- Points 1-5 east, points 6-7 west

For T006: 178.62, 179.20, 179.78, then -179.62, -179.04, -178.46, -177.87
- Points 1-3 east, points 4-7 west

For T007: 177.92, 178.69, 179.47, then -179.74, -178.97, -178.19
- Points 1-3 east, points 4-6 west

For T008: 179.40, 179.67, 179.94, then -179.77, -179.50, -179.23
- Points 1-3 east, points 4-6 west

For T009: 178.06, 178.56, 179.05, 179.54, then -179.95, -179.46
- Points 1-4 east, points 5-6 west

For T010: 177.60, 179.12, then -179.34, -177.82
- Points 1-2 east, points 3-4 west

Alright, so the approach is:
1. For features crossing the antimeridian (where consecutive points go from positive to negative longitude or vice versa), split the LineString into segments at the antimeridian.
2. For features not crossing, just wrap them as a single-segment MultiLineString.
3. Convert to EPSG:3460 (Fiji Map Grid).
4. Calculate length in meters.
5. Output as GeoJSON with WGS84... wait, the user says "convert them to Fiji's national metric grid" - so the output geometry should be in EPSG:3460. But GeoJSON typically uses WGS84 (EPSG:4326). However, GeoJSON can have non-WGS coordinates if specified.

Actually, looking at the user's request again: "convert them to Fiji's national metric grid, and give me each transect as one MultiLineString feature with a length_m attribute in metres"

So the output GeoJSON should have geometries in EPSG:3460 (Fiji Map Grid, which is in meters). GeoJSON spec says coordinates should be in WGS84, but many tools accept custom CRS. Let me include the CRS in the output.

Let me write a Python script to handle this.

The approach for splitting at the antimeridian:
- I'll iterate through the coordinates of each LineString
- When I encounter a transition from positive to negative longitude (or vice versa), I split there
- I need to handle the antimeridian crossing properly

Actually, the simplest approach: group consecutive points by which "side" of the antimeridian they're on. Points with lon > 0 are on the east side, lon < 0 are on the west side. When we transition, we start a new line.

But wait - the coordinates naturally go from one side to the other. The transects are survey paths that physically cross the 180° meridian. So I need to keep them as they are, but split at the crossing point so that each part is on one side.

Actually, for rendering purposes, the issue is that a line from (179.9, ...) to (-179.9, ...) goes the long way around (-180 degrees of longitude) instead of the short way (just 0.2 degrees crossing the date line). To fix this, we need to split the line at the antimeridian (180°), and potentially shift longitude values on one side.

Wait, let me think again. The coordinates are in WGS84. A line from 179.9 to -179.9 - does it go eastward (passing through 180) or westward (going the long way around)? GeoJSON rendering engines typically just connect the dots, so a line from 179.9 to -179.9 goes the long way around (through 0° longitude).

To fix: we split at the antimeridian. The points with positive longitude stay on one side (east side), and points with negative longitude go on the other side (west side). But for a continuous path, we need to actually insert a point at the antimeridian where the line crosses it.

Hmm, but for a truly short local segment that crosses the antimeridian, the points near 180°E might go from e.g. 179.5 to -179.8 - these are actually close together in reality (just 0.7° apart across the meridian). The problem is that the coordinates jump from positive to negative.

For a simple fix to make it render correctly, I need to:
1. Take the LineString coordinates
2. Split into groups where all longitudes have the same sign (or where they're continuous)
3. But actually, for points that are on the west side (lon < 0), we could also add 360 to make them > 180... but that changes the coordinate system.

Let me think about this differently. The simplest approach for fixing the antimeridian issue:

For each LineString that crosses the antimeridian:
- Separate the coordinates into groups: positive longitude group and negative longitude group
- Each group becomes a separate LineString in a MultiLineString
- But this loses the connection point at the meridian

Actually, a better approach: 
1. For each LineString, go through coordinate by coordinate
2. Track which "side" each point is on (east: lon>0, west: lon<0)
3. When switching sides, we don't add a crossing point - we just split the line at the transition
4. The result is a MultiLineString with multiple parts

But this might leave a gap at the antimeridian. However, for rendering purposes, the user just said "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path." So splitting at the transition point is fine.

Let me code this up.

Actually wait - I need to be more careful. Looking at the data again:

T001: [178.94, 179.45, 179.97, -179.51, -178.99, -178.48]
- Points 1-3: East side (positive lons)
- Points 4-6: West side (negative lons)
- The path goes from ~179.97 to ~-179.51 - these are actually close (only ~0.48° apart across the meridian)
- Split into: LineString([178.94, 179.45, 179.97]) and LineString([-179.51, -178.99, -178.48])

That should work for rendering. But wait, will the conversion to EPSG:3460 handle this correctly? Let's see.

EPSG:3460 is Fiji Map Grid. If we have a point at lon=179.97 and another at lon=-179.51, when converted to Fiji Map Grid, these should be close together (near the antimeridian in western Fiji). So yes, splitting and then converting should work fine.

Let me write the script.

Actually, I realize there's a subtlety. Let me check: for T008, the coordinates are: [179.40, 179.67, 179.94, -179.77, -179.50, -179.23]. So the transition is from 179.94 to -179.77. That's a span of about 0.29° (since 180 - 179.94 = 0.06 and 180 + (-179.77) = 0.23, total 0.29°). So yes, this is a short local segment crossing the antimeridian.

Now let me code this.

For features that don't cross the antimeridian but are already LineStrings, I need to make them MultiLineString with one part.

Let me write the script:
1:16
Bash python3 -c "
python3 -c "
import pyproj
crs = pyproj.CRS('EPSG:3460')
print('Area:', crs.area_of_use)
print('Name:', crs.name)
print('Type:', crs.type_name)
print('Coordinate system:', crs.coordinate_system)
"
Area: - name: Fiji - onshore. Includes Viti Levu, Vanua Levu, Taveuni, the Yasawa Group, the Kadavu Group, the Lau Islands and Rotuma Islands.
- bounds: (176.81, -20.81, -178.15, -12.42)
Name: Fiji 1986 / Fiji Map Grid
Type: Projected CRS
Coordinate system: cartesian
2:29
The "Fiji Map Grid" (EPSG:3460) is the right national metric grid for Fiji. Now I need to:

1. Fix the problematic geometries that cross the antimeridian (180° longitude) - these are the ones where coordinates jump from e.g. 179.9° to -179.9°, which renders as a globe-spanning arc.
2. All features that have `crosses_antimeridian_flag: true` cross the antimeridian.
3. Convert to Fiji Map Grid (EPSG:3460).
4. Compute length in metres.
5. Ensure each feature is a MultiLineString.
6. Carry over transect_id, vessel, survey_date.

Let me write a Python script that:
- Reads the GeoJSON
- For antimeridian-crossing features, splits the LineString at the antimeridian (180°) so the coordinates are re-centered to a continuous range (e.g., shift easting by 360° for the western side)
- Projects to EPSG:3460
- Calculates length
- Writes output as GeoJSON

Actually, let me think more carefully. The problem is that some LineStrings have coordinates that span east and west of the antimeridian. For instance, T001 has:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

When rendered, the line goes from 179.97 to -179.51, which wraps around the entire globe instead of crossing the dateline locally.

The fix: I need to split the LineString at the antimeridian (180°). Points east of 180° (positive lon < 180) and points west of 180° (negative lon) need to be in separate segments. But I also need to handle the case where the crossing is at the antimeridian.

For a proper fix:
- I'll iterate through the coordinates of each line
- When I detect a jump across the antimeridian (e.g., from ~180 to ~-180 or vice versa), I insert a point at the antimeridian (or split the line into segments)
- Then shift the negative longitudes by +360° for the western side to make them continuous with the eastern side

Actually, a common approach is:
1. Split the LineString into segments at the antimeridian crossings
2. For each segment, if it's west of 180°, add 360° to the longitudes to make them continuous
3. Then project

Let me think about a simpler approach. Since we have the crossing flag, for those features:

For each pair of consecutive points (lon1, lat1) -> (lon2, lat2):
- If lon1 and lon2 are on opposite sides of the antimeridian (say lon1 > 170 and lon2 < -170, or vice versa), that's a crossing
- We need to split the line at the antimeridian

Actually, the cleanest approach:
1. For each coordinate in the line, if lon < 0, add 360 to make it continuous (so -179 becomes 181, etc.)
2. Then the coordinates form a continuous line across the 180° line
3. Project to Fiji Map Grid
4. Calculate length
5. But for output, we need to keep the coordinates as-is (with +/- 180° range) or... actually, since we're projecting to FMG, the projected coordinates will be fine regardless of whether we shift.

Wait, no. The projection to FMG should handle it correctly if we first make the longitudes continuous. Let me think...

If I have coordinates [179.97, -17.61] and [-179.51, -17.60], and I project both to FMG, the first point will be near the antimeridian on the east side, and the second near it on the west side. The projected points will be far apart because the FMG projection isn't designed to wrap around the globe.

So I need to:
1. For antimeridian-crossing features, split into segments on each side of the antimeridian
2. For the side with negative longitudes, add 360 to make them continuous
3. Then project each segment
4. Combine into a MultiLineString

Let me code this up. 

The splitting logic:
- Iterate through the coordinates
- When crossing from east to west (or west to east), interpolate a point at 180° (or -180°)
- Add the segment up to the crossing point

Actually, let me use shapely to help with this. I can use the `shapely.ops.split` or similar, but it might be easier to just do it manually.

Here's my approach:

For each feature that crosses the antimeridian:
1. Create separate lists of coordinates for each "side" of the antimeridian
2. When the line crosses from east to west, split into two segments
3. For the western side, add 360 to all longitudes (making them > 180, continuous with the east)

For each feature that doesn't cross:
- Just keep it as is (but make sure it's a MultiLineString)

Then for ALL features:
- Convert to MultiLineString
- Project to EPSG:3460
- Calculate length
- Write output

Let me write the script.

Actually, a simpler way using shapely's transform:

I can use:
1. For antimeridian-crossing features: split at the 180° meridian using a line (0, -90) to (0, 90) but at lon=180... actually better to just do the manual approach.

Let me write the script step by step.

```python
import json
import geopandas as gpd
import shapely
from shapely.geometry import LineString, MultiLineString, box, Point
from pyproj import Transformer
import numpy as np

# Read the GeoJSON
with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

features = data['features']

# Define CRS
wgs84 = 'EPSG:4326'
fmg = 'EPSG:3460'
transformer = Transformer.from_crs(wgs84, fmg, always_xy=True)

def split_at_antimeridian(coords):
    """Split a list of coordinates at the antimeridian (180°).
    Returns list of lists of coordinates, each being a continuous segment."""
    segments = []
    current_segment = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Check if crossing the antimeridian
        # If we're going from east to west across 180
        if lon1 > 170 and lon2 < -170:
            # Interpolate point at antimeridian
            # Linear interpolation
            # We need to find where the line crosses lon=180
            # Using linear interpolation between (lon1, lat1) and (lon2+360, lat2)
            lon2_adj = lon2 + 360
            # t where longitude = 180
            t = (180 - lon1) / (lon2_adj - lon1)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            # End the current segment at the antimeridian
            current_segment.append((180.0, lat_at_180))
            segments.append(current_segment)
            
            # Start a new segment from the antimeridian
            # For the western side, add 360 to make continuous
            current_segment = [(-180.0, lat_at_180), (lon2, lat2)]
            
        elif lon1 < -170 and lon2 > 170:
            # Crossing from west to east
            lon1_adj = lon1 + 360
            # t where longitude = 180
            t = (180 - lon1_adj) / (lon2 - lon1_adj)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            # End current segment at antimeridian (west side, using adjusted lon)
            current_segment.append((180.0, lat_at_180))
            segments.append(current_segment)
            
            # Start new segment from antimeridian (east side)
            current_segment = [(-180.0, lat_at_180), (lon2, lat2)]
        else:
            current_segment.append((lon2, lat2))
    
    if current_segment:
        segments.append(current_segment)
    
    return segments

def adjust_west_side(coords):
    """For coordinates west of the antimeridian (negative longitudes), add 360.
    But only for segments that started on the east side and crossed to west.
    Actually, for each coordinate, if lon < 0, we add 360 to make it continuous."""
    result = []
    for lon, lat in coords:
        if lon < 0:
            result.append((lon + 360, lat))
        else:
            result.append((lon, lat))
    return result
```

Hmm, actually I need to think more carefully about this. Let me reconsider.

When a line goes from (179.9, -17.6) to (-179.5, -17.6), the actual survey path crosses the 180° meridian. The real-world path is a short segment crossing the dateline.

To handle this:
1. I should split the LineString into segments that don't cross the antimeridian.
2. For each segment that's west of the dateline, I should add 360 to the longitudes so they become continuous with the east side.
3. Or... I should just add 360 to ALL negative longitudes in the source coordinates, making them continuous (e.g., -179.5 becomes 180.5). Then the entire line becomes a continuous path that can be projected.

Wait, but if I just add 360 to all negative longitudes, the points that are naturally west of the antimeridian (like in the non-crossing transects T011, T012, etc.) would be shifted incorrectly.

OK, so the approach needs to be:
- For features that DON'T cross the antimeridian: just project as-is
- For features that DO cross: split at the antimeridian, add 360 to the western-side coordinates, project each segment, combine into MultiLineString

Let me re-think.

Actually, for the western-side segments that result from splitting, I can just add 360 to all longitudes (since they were originally negative, they'll become > 180, making them continuous with the eastern side). Then I project those adjusted coordinates. The projected coordinates will be physically close to where they should be.

Let me write a cleaner script:

```python
import json
from shapely.geometry import LineString, MultiLineString
from pyproj import Transformer
import numpy as np

with open('/work/fiji_transects_wgs84.geojson') as f:
    data = json.load(f)

transformer = Transformer.from_crs('EPSG:4326', 'EPSG:3460', always_xy=True)

def project_coords(coords):
    """Project a list of (lon, lat) to FMG."""
    return [transformer.transform(lon, lat) for lon, lat in coords]

def split_line_at_antimeridian(coords):
    """
    Split a line crossing the antimeridian into segments.
    Returns list of segments (each being list of (lon, lat) tuples).
    For western-hemisphere points, adds 360 to lon for continuity.
    """
    segments = []
    current = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Detect antimeridian crossing
        # Going from east to west (lon positive -> negative, crossing 180)
        if lon1 > 170 and lon2 < -170:
            # Interpolate crossing point at lon=180
            # Adjust lon2 by +360 for interpolation
            lon2_adj = lon2 + 360.0
            t = (180.0 - lon1) / (lon2_adj - lon1)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            # End current segment at crossing point (with original lon=180)
            current.append((180.0, lat_cross))
            segments.append(current)
            
            # Start new segment from crossing point (with adjusted lon=180+360=540? 
            # No, use the adjusted value for the western segment)
            # For the western segment, all lons are adjusted +360
            # So the crossing point becomes (540, lat_cross) ... that's too big
            # Actually, let's use (180, lat_cross) as the boundary, but for the western segment
            # we'll adjust all longitudes by +360 later.
            current = [(lon2, lat2)]
            
        # Going from west to east (lon negative -> positive, crossing -180)
        elif lon1 < -170 and lon2 > 170:
            lon1_adj = lon1 + 360.0
            t = (180.0 - lon1_adj) / (lon2 - lon1_adj)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            current.append((-180.0, lat_cross))
            segments.append(current)
            
            current = [(lon2, lat2)]
        else:
            current.append((lon2, lat2))
    
    if current:
        segments.append(current)
    
    return segments

def adjust_segment(coords, side):
    """If side is 'west', add 360 to all longitudes."""
    if side == 'west':
        return [(lon + 360.0, lat) for lon, lat in coords]
    return coords
```

Hmm, this is getting complicated. Let me think of a simpler approach.

For antimeridian-crossing lines:
1. I'll first add 360 to ALL negative longitudes in the original coordinates. This makes the entire line continuous (e.g., -179.51 becomes 180.49, which follows logically after 179.97).
2. Then I project the whole continuous line.
3. Since it's continuous, the projected coordinates will be correct.
4. Then for the output GeoJSON, I need to... well, we're outputting projected coordinates (FMG), so there's no need to convert back. The projected coordinates will naturally be correct.

Wait, but the issue is that the original geometries have coordinates that jump. If I just add 360 to negative lons for all features, the non-crossing ones that are entirely in the western hemisphere (like T011 with lons around -177) would be shifted to 183°, which is wrong.

So I need to handle crossing features specially. For crossing features:
1. For ALL coordinates, if lon < 0, add 360. This makes the line continuous.
2. Project this continuous line.
3. Calculate length.

But wait, for T001, the original coordinates are:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

After adding 360 to negatives:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [180.49, -17.60], [181.00, -17.58], [181.52, -17.58]

This is now a continuous line going eastward, and it can be projected to FMG correctly.

But then the projected geometry is just a single LineString, not a MultiLineString. The instruction says "Every feature must come out as a MultiLineString". For non-crossing features, I can just wrap them in a MultiLineString. For crossing features, the result of projecting the continuous line might still be a single line - but I could split at the antimeridian to create a multi-part geometry.

Actually, re-reading the instructions: "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path." So the crossing transects should be multi-part (split at the antimeridian).

Let me go with this approach:
1. For each crossing feature, split at the antimeridian into separate segments
2. For western segments, add 360 to longitudes
3. Project each segment
4. Combine into MultiLineString
5. Calculate total length
6. For non-crossing features, project and wrap in MultiLineString

Let me write it cleanly.

```python
import json
from shapely.geometry import LineString, MultiLineString, Point
from pyproj import Transformer
import numpy as np

with open('/work/fiji_transects_wgs84.geojson') as f:
    data = json.load(f)

transformer = Transformer.from_crs('EPSG:4326', 'EPSG:3460', always_xy=True)

def project_coord(lon, lat):
    return transformer.transform(lon, lat)

def split_at_180(coords):
    """
    Split a list of (lon, lat) coordinates at the 180th meridian.
    Returns segments where each segment is entirely on one side.
    Western side segments have their longitudes adjusted by +360.
    """
    if not coords:
        return []
    
    segments = []
    current_seg = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Check for antimeridian crossing
        crossing = False
        if lon1 > 0 and lon2 < 0 and abs(lon1 - (lon2 + 360)) < 20:
            # East to west crossing
            crossing = True
            lon2_adj = lon2 + 360
            t = (180 - lon1) / (lon2_adj - lon1)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            current_seg.append((180.0, lat_at_180))
            segments.append(current_seg)
            
            current_seg = [(lon2, lat2)]
            
        elif lon1 < 0 and lon2 > 0 and abs((lon1 + 360) - lon2) < 20:
            # West to east crossing
            crossing = True
            lon1_adj = lon1 + 360
            t = (180 - lon1_adj) / (lon2 - lon1_adj)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            current_seg.append((-180.0, lat_at_180))
            segments.append(current_seg)
            
            current_seg = [(lon2, lat2)]
        else:
            current_seg.append((lon2, lat2))
    
    if current_seg:
        segments.append(current_seg)
    
    return segments

# Hmm, but how do I know which side is which for adjustment?
```

Let me simplify by using a different approach. Instead of splitting at the antimeridian, I'll just use the continuous coordinates (adding 360 to negatives) for projection, and then split the resulting FMG line at the FMG equivalent of the antimeridian. But that's also complex.

Actually, let me re-read the instructions more carefully:

"the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path"

So for a transect that crosses the antimeridian, I need to create a MultiLineString where each part is on one side of the dateline. The "actual survey path" is a short segment crossing the dateline, not a globe-spanning arc. 

Let me try another approach: create the line as a continuous path (by adding 360 to negative lons), project that, then split the projected line at the point where the original crosses the antimeridian.

Actually, the simplest approach:

1. For crossing features, create a continuous LineString by adding 360 to negative longitudes
2. Project this LineString to FMG
3. Create two parts from it: one for the eastern portion and one for the western portion  
4. Combine into MultiLineString

But how to split the projected line? I could track the index where the crossing happens.

Let me just do this step by step in the code:

```python
def fix_antimeridian(coords):
    """
    Process coordinates that cross the antimeridian.
    Returns a list of segments (list of lists of (lon, lat)) where
    western-hemisphere coordinates have been adjusted by +360 for continuity.
    """
    segments = []
    current_seg = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Check if crossing antimeridian (going east to west)
        if lon1 > 170 and lon2 < -170:
            # Interpolate at 180
            lon2_adj = lon2 + 360
            t = (180 - lon1) / (lon2_adj - lon1)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            current_seg.append((180.0, lat_at_180))
            segments.append(current_seg)  # East side segment
            
            # Start west side segment
            current_seg = [(-180.0 + 360, lat_at_180), (lon2 + 360, lat2)]
            # ^^ adjust western lons by +360
            
        elif lon1 < -170 and lon2 > 170:
            # West to east crossing
            lon1_adj = lon1 + 360
            t = (180 - lon1_adj) / (lon2 - lon1_adj)
            lat_at_180 = lat1 + t * (lat2 - lat1)
            
            current_seg.append((-180.0 + 360, lat_at_180))
            segments.append(current_seg)  # West side segment (adjusted)
            
            # Start east side segment
            current_seg = [(180.0, lat_at_180), (lon2, lat2)]
            
        else:
            # Determine if we're on the east or west side for adjustment
            # For the first point of a new segment after a split, we adjust if on west side
            current_seg.append((lon2, lat2))
    
    if current_seg:
        segments.append(current_seg)
    
    return segments
```

Wait, I'm overcomplicating this. Let me simplify.

Actually, I think the simplest correct approach is:

For antimeridian-crossing features:
1. Split the original LineString at the antimeridian (180°)
2. For the western-side part, add 360° to all longitudes (making them continuous)
3. Project each part separately to FMG
4. Create a MultiLineString from the parts
5. Calculate total length

The key insight: when I split at the antimeridian and add 360 to western lons, both segments are in a continuous longitude space where the eastern segment ends at 180° and the western segment starts at 180° (actually 180+360=540°, but that's fine for the FMG projection since it's just a mathematical coordinate).

Wait, no. If the western segment starts at 180+360=540°, that's way too far. Let me think again.

OK, the issue is: after splitting, the eastern segment has coordinates like [..., (179.9, -17.6), (180.0, -17.6)] and the western segment starts at [(-180.0, -17.6), (-179.5, -17.6), ...]. 

If I add 360 to the western segment's longitudes, it becomes [(180.0, -17.6), (180.5, -17.6), ...]. Now both segments meet at 180° longitude in the adjusted space, and the projected coordinates should be continuous and correct.

Actually, let me try this even simpler approach:

For ALL transects (both crossing and non-crossing):
1. If the transect crosses the antimeridian, adjust all negative longitudes by +360 for continuity
2. Project the continuous line
3. Calculate length

But then the output would be a single LineString (or just wrapped as MultiLineString). The instruction says "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path."

Hmm, but the "actual survey path" crossing the antimeridian IS a short path. If I project the continuous line, the projected coordinates will show that short path. But as a single line, not multi-part.

Let me re-read: "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path"

I think "multi-part" here means each transect should be a MultiLineString feature. Even non-problematic ones. And the problematic ones should be split at the antimeridian to show the actual path (which is short in real-world terms, not wrapping around the globe).

So the approach is:
1. For all features, ensure output is MultiLineString
2. For antimeridian-crossing features, split at the antimeridian, creating 2 (or more) parts in the MultiLineString
3. For non-crossing features, just wrap the single LineString in a MultiLineString

Let me write this cleanly now.

```python
import json
from shapely.geometry import LineString, MultiLineString
from pyproj import Transformer
import numpy as np

with open('/work/fiji_transects_wgs84.geojson') as f:
    data = json.load(f)

transformer = Transformer.from_crs('EPSG:4326', 'EPSG:3460', always_xy=True)

def process_crossing(coords):
    """
    Process coordinates that cross the antimeridian.
    Returns list of projected line segments (list of [(x,y),...]).
    """
    segments = []  # list of lists of (lon, lat)
    current = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Detect east-to-west crossing at 180°
        if lon1 > 170 and lon2 < -170:
            # Interpolate crossing point
            lon2_adj = lon2 + 360.0
            t = (180.0 - lon1) / (lon2_adj - lon1)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            # Close eastern segment at 180°
            current.append((180.0, lat_cross))
            segments.append(current)
            
            # Start western segment: use adjusted longitudes (+360)
            current = [(180.0, lat_cross), (lon2 + 360.0, lat2)]
            
        # Detect west-to-east crossing at -180°
        elif lon1 < -170 and lon2 > 170:
            # For interpolation, adjust lon1 by +360
            lon1_adj = lon1 + 360.0
            t = (180.0 - lon1_adj) / (lon2 - lon1_adj)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            # Close western segment at adjusted 180°
            current.append((180.0, lat_cross))
            segments.append(current)
            
            # Start eastern segment
            current = [(180.0, lat_cross), (lon2, lat2)]
        else:
            # Add to current segment, adjusting lon if on western side
            # First, determine if this segment started on the west side
            # Check if there's already a segment in segments that ended at 180
            # Actually, we need to track whether current is west-side
            current.append((lon2, lat2))
    
    if current:
        segments.append(current)
    
    # Now project each segment
    projected_segments = []
    for seg in segments:
        # Check if this is a western segment (first point lon >= 180 or > 180)
        # Actually, western segments have had 360 added, so lon > 180
        proj = [transformer.transform(lon, lat) for lon, lat in seg]
        projected_segments.append(proj)
    
    return projected_segments
```

Wait, I realize there's a subtlety. When I start a western segment after crossing, the first point of the western segment is at (180.0, lat_cross) - but I set it as (180.0, lat_cross) for the eastern segment too. So both segments share the crossing point. That's fine.

But if the current segment is on the western side (all lons adjusted +360), subsequent points within that segment will have their original negative lons. I need to track whether I'm on the west side and adjust accordingly.

Let me track a `west_side` flag:

```python
def process_crossing(coords):
    segments = []
    current = [coords[0]]
    west_side = coords[0][0] < 0  # Start tracking
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # East to west crossing
        if lon1 > 170 and lon2 < -170:
            lon2_adj = lon2 + 360.0
            t = (180.0 - lon1) / (lon2_adj - lon1)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            current.append((180.0, lat_cross))
            segments.append(current)
            
            # Start western side (adjusted)
            current = [(180.0, lat_cross), (lon2 + 360.0, lat2)]
            west_side = True
            
        # West to east crossing
        elif lon1 < -170 and lon2 > 170:
            lon1_adj = lon1 + 360.0
            t = (180.0 - lon1_adj) / (lon2 - lon1_adj)
            lat_cross = lat1 + t * (lat2 - lat1)
            
            current.append((180.0, lat_cross))
            segments.append(current)
            
            current = [(180.0, lat_cross), (lon2, lat2)]
            west_side = False
        else:
            if west_side:
                current.append((lon2 + 360.0, lat2))
            else:
                current.append((lon2, lat2))
    
    if current:
        segments.append(current)
    
    # Project each segment
    projected_segments = []
    for seg in segments:
        proj = [transformer.transform(lon, lat) for lon, lat in seg]
        projected_segments.append(proj)
    
    return projected_segments
```

This looks good. Let me also handle non-crossing features.

For non-crossing features, they may be entirely in the eastern hemisphere (all positive lons) or entirely in the western hemisphere (all negative lons). I just project them as-is and wrap in MultiLineString.

Let me write the full script now.
2:29
2:29
Bash python3 -c "
python3 -c "
import json
with open('/work/fiji_transects_wgs84.geojson') as f:
    data = json.load(f)

# Show crossing vs non-crossing
for feat in data['features']:
    tid = feat['properties']['transect_id']
    cross = feat['properties']['crosses_antimeridian_flag']
    coords = feat['geometry']['coordinates']
    lons = [c[0] for c in coords]
    print(f'{tid}: crosses={cross}, lons=[{min(lons):.2f}, {max(lons):.2f}], npts={len(coords)}')
"
T001: crosses=True, lons=[-179.51, 179.97], npts=6
T002: crosses=True, lons=[-179.62, 179.73], npts=4
T003: crosses=True, lons=[-179.89, 179.29], npts=5
T004: crosses=True, lons=[-179.79, 179.54], npts=6
T005: crosses=True, lons=[-179.58, 179.85], npts=7
T006: crosses=True, lons=[-179.63, 179.79], npts=7
T007: crosses=True, lons=[-179.75, 179.47], npts=6
T008: crosses=True, lons=[-179.78, 179.95], npts=6
T009: crosses=True, lons=[-179.96, 179.55], npts=6
T010: crosses=True, lons=[-179.35, 179.13], npts=4
T011: crosses=False, lons=[-177.60, -176.86], npts=7
T012: crosses=False, lons=[176.31, 176.46], npts=4
T013: crosses=False, lons=[-178.20, -176.71], npts=4
T014: crosses=False, lons=[176.54, 177.83], npts=5
T015: crosses=False, lons=[-178.43, -177.28], npts=7
T016: crosses=False, lons=[178.45, 178.95], npts=5
T017: crosses=False, lons=[-177.69, -176.95], npts=7
T018: crosses=False, lons=[176.11, 176.83], npts=5
T019: crosses=False, lons=[176.37, 178.01], npts=5
T020: crosses=False, lons=[176.87, 178.75], npts=5
T021: crosses=False, lons=[176.26, 177.97], npts=4
T022: crosses=False, lons=[177.12, 177.44], npts=6
T023: crosses=False, lons=[-177.91, -177.19], npts=6
T024: crosses=False, lons=[-179.11, -176.83], npts=6
T025: crosses=False, lons=[176.57, 176.58], npts=4
T026: crosses=False, lons=[177.69, 178.63], npts=6
T027: crosses=False, lons=[-178.87, -177.77], npts=6
T028: crosses=False, lons=[-178.15, -176.58], npts=5
T029: crosses=False, lons=[-178.08, -176.56], npts=5
T030: crosses=False, lons=[-179.03, -177.77], npts=7
2:55
Now I understand the problem. Transects that cross the antimeridian (180° longitude) have coordinates that jump from ~+179° to ~-179°, which causes a line to wrap around the entire globe in a rendering. The fix is to split these lines into segments that don't cross the antimeridian, by adding a "break" at the antimeridian.

The approach:
1. For lines that cross the antimeridian (where longitude values jump from positive to negative or vice versa), I need to split them at the 180° meridian.
2. Convert all coordinates to Fiji Map Grid (EPSG:3460).
3. Calculate length in meters for each transect (sum of segment lengths).
4. Output as MultiLineString features with length_m attribute.

Let me write a Python script to do this.

For the antimeridian crossing fix:
- I'll iterate through each pair of consecutive coordinates.
- If the line segment crosses the antimeridian (i.e., the longitudes are on opposite sides of the dateline, with a large gap), I need to insert an intermediate point at the antimeridian.
- Actually, the best approach: for each pair of consecutive points where there's a longitude jump > 180° or < -180°, I can interpolate the intersection with the 180° meridian and split the line there.

For crossing the antimeridian, the standard approach:
- If lon1 < 0 and lon2 > 0 (or vice versa), and abs(lon1 - lon2) > 180, then it crosses the dateline.
- We need to split at the antimeridian (lon = ±180°).

Let me think about the splitting more carefully.

For a segment from (lon1, lat1) to (lon2, lat2):
- If lon1 is positive and lon2 is negative (or vice versa), and the absolute difference in longitude is > 180°, it's crossing the antimeridian.
- Actually, the simplest test: if (lon1 >= 0 and lon2 < 0) or (lon1 < 0 and lon2 >= 0), and the difference |lon1 - lon2| > 180°, then it crosses.
- We need to find where the line crosses the 180° meridian.

For the interpolation: we want to find λ such that lon = 180° (or -180°).
Since we're working on a sphere, we can do linear interpolation:
- If going from lon1 to lon2 across the antimeridian:
  - We need to adjust one of the longitudes by adding/subtracting 360 to make the interpolation smooth.
  - For example, if lon1 = 179 and lon2 = -179, we can adjust lon2 to 181 (by adding 360).
  - Then we find t where lon = 180: t = (180 - lon1) / (lon2 - lon1)
  - lat at that point: lat = lat1 + t * (lat2 - lat1)

Let me code this up.

Actually, I need to be more precise. Let me think about the geometry.

The problem: Lines with coordinates like:
`[179.97, -17.61] -> [-179.51, -17.60]`

This spans from 179.97° to -179.51° (equivalent to 180.49° going the other way). When rendered in a standard web map, it wraps the long way around the globe.

The fix: split this into two segments:
1. [179.97, -17.61] -> [180, lat_intersection]
2. [-180, lat_intersection] -> [-179.51, -17.60]

Or equivalently, in a projected coordinate system that handles the dateline properly, we might not need to split. But the issue is that GeoJSON uses unprojected coordinates, so rendering in a web map will try to connect them directly.

Let me write the solution.

Actually, let me think about this differently. Rather than splitting at the antimeridian in WGS84 and then projecting, I could:

1. For each coordinate pair, if it wraps over the antimeridian, adjust the longitudes by ±360 to make them continuous.
2. Project to Fiji Map Grid.
3. Then split the projected line at the antimeridian equivalent to get proper multi-part geometries.

Hmm, but actually the simplest approach might be:
1. Split crossing lines at the antimeridian in WGS84 (inserting points at the dateline).
2. Coordinate collections on each side form separate parts of the multi-line.
3. Project each to Fiji Map Grid.

Let me think about what "split at antimeridian" means exactly.

For a line going from lon1 (pos, say 179.97) to lon2 (neg, say -179.51):
- It crosses the antimeridian.
- We need to find the point where longitude = 180 (or -180, same thing).
- The first part goes from lon1 to 180, the second from -180 to lon2.

Let me code this step by step.

For finding the intersection with the antimeridian:
- Let lon1, lat1 and lon2, lat2.
- If lon1 > 0 and lon2 < 0 (crossing antimeridian from east to west):
  - Adjust: lon2_adj = lon2 + 360
  - t = (180 - lon1) / (lon2_adj - lon1)
  - lat_int = lat1 + t * (lat2 - lat1)
  - Intersection point is (180, lat_int) and then the second part starts from (-180, lat_int)
  
- If lon1 < 0 and lon2 > 0 (crossing from west to east):
  - Adjust: lon2_adj = lon2 - 360
  - t = (-180 - lon1) / (lon2_adj - lon1)
  - lat_int = lat1 + t * (lat2 - lat1)
  - Intersection point is (-180, lat_int) and then second part from (180, lat_int)

Wait, actually let me think more carefully. The antimeridian is at ±180°. When we split, we get one part ending at (180, lat) and the other starting at (-180, lat). In WGS84 coordinates, (180, lat) and (-180, lat) represent the same point.

Let me write the full solution script.
2:55
Write /work/solve.py
content · 170 lines · py
#!/usr/bin/env python3
"""
Fix antimeridian-crossing transects, project to Fiji Map Grid (EPSG:3460),
and output as MultiLineString features with length_m attribute.
"""
import json
import math
from copy import deepcopy
from pyproj import Transformer

# ---------- Coordinate/geometry helpers ----------

def crosses_antimeridian(lon1, lon2):
    """Return True if the great-circle-ish segment between two lons crosses the antimeridian."""
    return (lon1 >= 0 and lon2 < 0 and abs(lon1 - lon2) > 180) or \
           (lon1 < 0 and lon2 >= 0 and abs(lon1 - lon2) > 180)

def antimeridian_intersection(p1, p2):
    """
    Given two points (lon, lat) where the segment crosses the antimeridian,
    return the intersection point (lon, lat) at the antimeridian (lon=180 or -180).
    The result is the point on the antimeridian with the appropriate sign.
    """
    lon1, lat1 = p1
    lon2, lat2 = p2

    if lon1 >= 0 and lon2 < 0:
        # East to West crossing: adjust lon2 by +360 to make continuous
        lon2_adj = lon2 + 360
        target_lon = 180.0
        # Interpolate to find where longitude = target_lon
        t = (target_lon - lon1) / (lon2_adj - lon1)
        lat_int = lat1 + t * (lat2 - lat1)
        return (180.0, lat_int), (-180.0, lat_int)
    else:
        # West to East crossing: adjust lon2 by -360
        lon2_adj = lon2 - 360
        target_lon = -180.0
        t = (target_lon - lon1) / (lon2_adj - lon1)
        lat_int = lat1 + t * (lat2 - lat1)
        return (-180.0, lat_int), (180.0, lat_int)

def split_line_at_antimeridian(coords):
    """
    Split a LineString (list of (lon, lat) tuples) at the antimeridian.
    Returns a list of lists of coordinates, each list being one part of the split.
    """
    parts = []
    current_part = [coords[0]]

    for i in range(1, len(coords)):
        prev = coords[i - 1]
        curr = coords[i]

        if crosses_antimeridian(prev[0], curr[0]):
            # Find intersection with antimeridian
            p_180, p_neg180 = antimeridian_intersection(prev, curr)

            # Close the current part
            current_part.append(p_180)
            parts.append(current_part)

            # Start a new part
            current_part = [p_neg180, curr]
        else:
            current_part.append(curr)

    parts.append(current_part)
    return parts

def haversine_distance_m(p1, p2):
    """
    Compute great-circle distance in meters between two (lon, lat) points.
    """
    lon1, lat1 = map(math.radians, p1)
    lon2, lat2 = map(math.radians, p2)
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
    c = 2 * math.asin(math.sqrt(a))
    return c * 6371000  # Earth radius in meters


# ---------- Main processing ----------

# Load GeoJSON
with open('/work/fiji_transects_wgs84.geojson') as f:
    data = json.load(f)

# Build transformer: WGS84 -> Fiji Map Grid (EPSG:3460)
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

features_out = []

for feat in data['features']:
    props = feat['properties']
    coords_raw = feat['geometry']['coordinates']

    # Convert to list of (lon, lat) tuples
    coords = [(c[0], c[1]) for c in coords_raw]

    # Split at antimeridian if needed
    if props.get('crosses_antimeridian_flag', False):
        parts = split_line_at_antimeridian(coords)
    else:
        parts = [coords]

    # Also check if any non-flagged line actually crosses (safety check)
    # We already split flagged ones. Non-flagged shouldn't cross.

    # Project each part to Fiji Map Grid
    projected_parts = []
    for part in parts:
        projected_part = []
        for lon, lat in part:
            easting, northing = transformer.transform(lon, lat)
            projected_part.append((easting, northing))
        projected_parts.append(projected_part)

    # Compute length: sum of haversine distances between each consecutive point
    # (using original WGS84 coords for accuracy)
    length_m = 0.0
    for i in range(1, len(coords)):
        length_m += haversine_distance_m(coords[i - 1], coords[i])

    # Build the output feature
    out_feat = {
        "type": "Feature",
        "properties": {
            "transect_id": props["transect_id"],
            "vessel": props["vessel"],
            "survey_date": props["survey_date"],
            "length_m": round(length_m, 2)
        },
        "geometry": {
            "type": "MultiLineString",
            "coordinates": projected_parts
        }
    }
    features_out.append(out_feat)

# Build output GeoJSON
out_collection = {
    "type": "FeatureCollection",
    "name": "fiji_transects_fmg",
    "crs": {
        "type": "name",
        "properties": {
            "name": "urn:ogc:def:crs:EPSG::3460"
        }
    },
    "features": features_out
}

with open('/work/fiji_transects_fmg.geojson', 'w') as f:
    json.dump(out_collection, f, indent=2)

print("Done! Written /work/fiji_transects_fmg.geojson")

# Quick verification
with open('/work/fiji_transects_fmg.geojson') as f:
    result = json.load(f)
print(f"Features: {len(result['features'])}")
for feat in result['features']:
    tid = feat['properties']['transect_id']
    gtype = feat['geometry']['type']
    nparts = len(feat['geometry']['coordinates'])
    length = feat['properties']['length_m']
    print(f"  {tid}: {gtype}, {nparts} part(s), length={length:.1f}m")
Wrote /work/solve.py (5442 chars).
3:16
Let me think about this more carefully. The issue is with lines that cross the antimeridian. When you have a line like T001 that goes from 178.9 to -178.5 (via positive longitudes then negative), a naive rendering engine might see a huge jump from +179.97 to -179.51 and draw a line across the entire globe instead of crossing the International Date Line.

The solution is to split these lines at the antimeridian (180°/-180°) into separate segments, so each part stays on one side. Then project each part to Fiji Map Grid (EPSG:3460).

Let me verify my antimeridian intersection logic more carefully.

For T001 coordinates:
[ 178.94, -17.63 ]
[ 179.46, -17.62 ]
[ 179.97, -17.61 ]
[ -179.51, -17.60 ]  <- crosses from 179.97 to -179.51
[ -179.00, -17.58 ]
[ -178.48, -17.58 ]

The crossing is between point 3 (179.97, -17.61) and point 4 (-179.51, -17.60).

My function: lon1=179.97 >= 0, lon2=-179.51 < 0, and abs(179.97 - (-179.51)) = 359.48 > 180. So yes, it crosses.

I adjust lon2 to -179.51 + 360 = 180.49.
target_lon = 180.0
t = (180.0 - 179.97) / (180.49 - 179.97) = 0.03 / 0.52 ≈ 0.0577
lat_int = -17.61 + 0.0577 * (-17.60 - (-17.61)) = -17.61 + 0.0577 * 0.01 = -17.6094

So intersection at (180.0, -17.6094) and (-180.0, -17.6094). That seems right.

Part 1: [(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (180.0, -17.6094)]
Part 2: [(-180.0, -17.6094), (-179.51, -17.60), (-179.00, -17.58), (-178.48, -17.58)]

That looks correct! The rendered segments would be:
- Part 1: from Fiji eastward to the date line
- Part 2: from the date line westward to the last point

No huge global-spanning arc.

Now let me also think about the projection. EPSG:3460 is Fiji 1986 / Fiji Map Grid. Its bounds are (176.81, -20.81, -178.15, -12.42). So longitudes from about 176.81 to -178.15. 

The coordinates on the "east side" (near 180°) will be near 180° longitude, which is within bounds. The coordinates on the "west side" (near -180°) will be near -180° longitude, which is essentially the same as 180° but with negative sign. The Fiji Map Grid area includes up to -178.15, so -180° might be slightly outside. But the intersection point at -180° is essentially the antimeridian - points will be very close to Fiji. Let me check.

Actually, the intersection point at (180, -17.61) and (-180, -17.61) are both on the antimeridian. Since the Fiji Map Grid bounds extend to -178.15 (east/west), -180 is slightly outside the defined area. But it should project fine - it's just a few km away. The points near the antimeridian that are in our data go to about -178.48 for T001, which is within bounds.

Let me run the script and see what happens.
3:16
3:16
Bash cd /work && python3 solve.py
Done! Written /work/fiji_transects_fmg.geojson
Features: 30
  T001: MultiLineString, 2 part(s), length=272883.6m
  T002: MultiLineString, 2 part(s), length=216328.8m
  T003: MultiLineString, 2 part(s), length=351329.4m
  T004: MultiLineString, 2 part(s), length=372183.9m
  T005: MultiLineString, 2 part(s), length=361918.2m
  T006: MultiLineString, 2 part(s), length=393165.6m
  T007: MultiLineString, 2 part(s), length=413939.2m
  T008: MultiLineString, 2 part(s), length=146587.5m
  T009: MultiLineString, 2 part(s), length=263036.0m
  T010: MultiLineString, 2 part(s), length=489059.8m
  T011: MultiLineString, 1 part(s), length=78224.7m
  T012: MultiLineString, 1 part(s), length=84862.4m
  T013: MultiLineString, 1 part(s), length=179453.7m
  T014: MultiLineString, 1 part(s), length=148280.2m
  T015: MultiLineString, 1 part(s), length=124401.4m
  T016: MultiLineString, 1 part(s), length=53243.2m
  T017: MultiLineString, 1 part(s), length=152593.7m
  T018: MultiLineString, 1 part(s), length=112566.6m
  T019: MultiLineString, 1 part(s), length=179201.5m
  T020: MultiLineString, 1 part(s), length=215225.7m
  T021: MultiLineString, 1 part(s), length=230359.8m
  T022: MultiLineString, 1 part(s), length=51071.8m
  T023: MultiLineString, 1 part(s), length=81789.9m
  T024: MultiLineString, 1 part(s), length=250460.4m
  T025: MultiLineString, 1 part(s), length=108911.0m
  T026: MultiLineString, 1 part(s), length=100537.7m
  T027: MultiLineString, 1 part(s), length=129918.7m
  T028: MultiLineString, 1 part(s), length=172516.7m
  T029: MultiLineString, 1 part(s), length=205033.9m
  T030: MultiLineString, 1 part(s), length=163771.6m
3:37
Let me verify the output more carefully. I want to check:

1. All features are MultiLineString - looks good
2. The antimeridian-crossing ones (T001-T010) have 2 parts each - good
3. The non-crossing ones have 1 part each - good

But wait, I need to think about this more carefully. The crossing ones like T001 have coordinates like:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

The jump from 179.97 to -179.51 is what causes the problem. That segment goes from (179.97, -17.61) to (-179.51, -17.60). I need to split at the antimeridian (180°/-180°).

When splitting, the segment between (179.97, -17.61) and (-179.51, -17.60) crosses the antimeridian. I need to:
1. Find where the line crosses the antimeridian (both 180° and -180°)
2. Split the line there

The antimeridian intersection point should be at ±180° longitude. The latitude is interpolated linearly between the two points.

Let me verify this is correct by checking the intersection point calculation.

For T001:
- prev = (179.97, -17.61)
- curr = (-179.51, -17.60)

Since prev[0] >= 0 and curr[0] < 0, and abs(179.97 - (-179.51)) = 359.48 > 180, so it crosses.

lon1=179.97, lat1=-17.61
lon2=-179.51 (adjusted by +360) = 180.49, lat2=-17.60

t = (180 - 179.97) / (180.49 - 179.97) = 0.03 / 0.52 = 0.0577

lat_int = -17.61 + 0.0577 * (-17.60 - (-17.61)) = -17.61 + 0.0577 * 0.01 = -17.61 + 0.00058 = -17.6094...

So the intersection is at (180, -17.6094) and (-180, -17.6094).

That seems reasonable.

Now, let me think about the length calculation. I'm using haversine distance on the original coordinates. For the antimeridian crossers, I'm computing the length between consecutive points in the original coordinate list. But the original list has a jump from 179.97 to -179.51. The haversine formula should handle this correctly because it works with angles. Let me check...

haversine_distance_m((179.97, -17.61), (-179.51, -17.60)):
lon1 = 179.97° = 3.14 rad
lat1 = -17.61° = -0.307 rad
lon2 = -179.51° = -3.133 rad
lat2 = -17.60° = -0.307 rad

dlon = -179.51 - 179.97 = -359.48° = -6.274 rad... but wait, the formula uses radians and this would be a huge difference. The haversine formula would compute the great-circle distance between these two points, which should be correct since they're physically close (just on opposite sides of the date line).

Actually, let me recalculate: 
dlon = lon2 - lon1 = -179.51° - 179.97° = -359.48°

But in haversine, this would give sin(dlon/2) where dlon/2 = -179.74°, sin(-179.74°) = sin(179.74°) ≈ 0.009... hmm, that's about right.

Actually, the haversine formula should work fine because sin²(Δλ/2) where Δλ = -359.48°, sin(-179.74°) = sin(179.74°) = sin(180° - 0.26°) = sin(0.26°) ≈ 0.0045.

So it computes the correct small distance across the date line. Good.

But wait, I should double-check. Let me also look at T010 which goes from 177.60 to 179.13 to -179.35 to -177.82. The segments are:
(177.60, -17.28) -> (179.13, -17.47) - fine, no crossing
(179.13, -17.47) -> (-179.35, -17.65) - crosses! 179.13 to -179.35, difference = 358.48 > 180
(-179.35, -17.65) -> (-177.82, -17.84) - fine, no crossing

So this would be split into two parts:
Part 1: (177.60, -17.28) -> (179.13, -17.47) -> (180, lat_int)
Part 2: (-180, lat_int) -> (-179.35, -17.65) -> (-177.82, -17.84)

OK, the logic looks correct.

Let me also verify for T003 which has coordinates:
(177.64, -17.36) -> (178.46, -17.42) -> (179.29, -17.47) -> (-179.89, -17.53) -> (-179.06, -17.59)

The crossing is between 179.29 and -179.89. So:
Part 1: (177.64, -17.36) -> (178.46, -17.42) -> (179.29, -17.47) -> (180, lat_int)
Part 2: (-180, lat_int) -> (-179.89, -17.53) -> (-179.06, -17.59)

That looks correct.

Let me now verify the actual output file.
3:37
3:37
Read /work/fiji_transects_fmg.geojson
     1	{
     2	  "type": "FeatureCollection",
     3	  "name": "fiji_transects_fmg",
     4	  "crs": {
     5	    "type": "name",
     6	    "properties": {
     7	      "name": "urn:ogc:def:crs:EPSG::3460"
     8	    }
     9	  },
    10	  "features": [
    11	    {
    12	      "type": "Feature",
    13	      "properties": {
    14	        "transect_id": "T001",
    15	        "vessel": "Taveuni II",
    16	        "survey_date": "2025-08-15",
    17	        "length_m": 272883.64
    18	      },
    19	      "geometry": {
    20	        "type": "MultiLineString",
    21	        "coordinates": [
    22	          [
    23	            [
    24	              2020343.340583844,
    25	              3930017.4674364463
    26	            ],
    27	            [
    28	              2074969.3287187445,
    29	              3930832.111936373
    30	            ],
    31	            [
    32	              2129608.4030453707,
    33	              3931959.043532026
    34	            ],
    35	            [
    36	              2132641.3519591475,
    37	              3932030.6236885176
    38	            ]
    39	          ],
    40	          [
    41	            [
    42	              2132641.3519591503,
    43	              3932030.6236885176
    44	            ],
    45	            [
    46	              2184265.933617849,
    47	              3933178.8742693216
    48	            ],
    49	            [
    50	              2238946.8792888806,
    51	              3934438.0861816974
    52	            ],
    53	            [
    54	              2293630.6493575512,
    55	              3933970.5213900264
    56	            ]
    57	          ]
    58	        ]
    59	      }
    60	    },
<system-reminder>Showed lines 1-60 of 1256. Re-call Read with offset=61 to continue, or raise limit.</system-reminder>
4:01
Let me verify the geometry looks correct. For T001, the coordinates are:
- Original: [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

The antimeridian crossing would be between [179.97, -17.61] and [-179.51, -17.60].

After splitting, we should have:
Part 1: [178.94, -17.63] -> [179.46, -17.62] -> [179.97, -17.61] -> [180.0, intersection_lat]
Part 2: [-180.0, intersection_lat] -> [-179.51, -17.60] -> [-179.00, -17.58] -> [-178.48, -17.58]

Looking at the output:
Part 1 has 4 points (the first 3 original + cut at antimeridian = intersection at ~180°)
Part 2 has 4 points (cut at -180° + last 3 original)

Wait, that's 4 + 4 = 8 points but originally there were 6 points. So we added 2 intersection points. That makes sense.

But wait - let me check the coordinates more carefully. The last point of part 1 and the first point of part 2 should be very close (same point on different sides of the antimeridian).

Part 1 end: [2132641.35..., 3932030.62...]
Part 2 start: [2132641.35..., 3932030.62...]

These are very close but not exactly the same due to floating point. Let me look at the full precision:

Actually they ARE the same within the decimal places shown... let me check more carefully.

Actually, looking at line 36: 2132641.3519591475 and line 42: 2132641.3519591503 - these differ slightly. Let me check if this is an issue. Actually, they should be the same point (the intersection at the antimeridian, just expressed as 180° and -180°). When projected, they should produce the same easting/northing since 180°E and 180°W are the same meridian.

The difference is due to floating point in the interpolation. Let me fix this to ensure the split point is identical by computing it once and reusing.

Actually, looking more carefully: the two parts touch at the antimeridian, which is correct. The slight numerical difference (1.503e-11 vs 1.475e-11) is trivial and won't affect rendering. But let me check if the interpolation is correctly done.

Let me think about the antimeridian intersection function again.

For the crossing segment [179.97, -17.61] -> [-179.51, -17.60]:
- lon1 = 179.97, lat1 = -17.61107... (from original: -17.611072392823225)
- lon2 = -179.51379..., lat2 = -17.596198... (from original: -17.596198176111951)

Since lon1 >= 0 and lon2 < 0 and abs(lon1 - lon2) > 180:
- lon2_adj = -179.51379... + 360 = 180.4862...
- t = (180.0 - 179.97) / (180.4862... - 179.97) = 0.03 / 0.5162... ≈ 0.0581
- lat_int = -17.61107... + 0.0581 * (-17.59620... + 17.61107...)
         = -17.61107... + 0.0581 * 0.01487...
         ≈ -17.61020...

That seems reasonable.

Now, the function returns (180.0, lat_int), (-180.0, lat_int). When projected, these should give the same easting/northing since 180°E = 180°W.

Let me verify by checking the projection of both.

Actually, the slight difference might be from the fact that I first compute and then project. Let me check if the points are truly the same after projection.

Looking at the eastings: 2132641.3519591475 vs 2132641.3519591503 - difference of ~2.8e-9 meters (trivially small). This is floating point noise from the two-step process (interpolation then projection). Not an issue.

But wait, I want to double-check something. Let me look at T010 which has only 4 points and spans across the antimeridian.

T010 original: [177.60, -17.28], [179.13, -17.47], [-179.35, -17.65], [-177.82, -17.84]

Crossing between [179.13, -17.47] and [-179.35, -17.65].

After split:
Part 1: [177.60, -17.28] -> [179.13, -17.47] -> [180, lat_int]
Part 2: [-180, lat_int] -> [-179.35, -17.65] -> [-177.82, -17.84]

That gives 3 + 3 = 6 points from 4 original. Makes sense.

Let me also verify that the length calculation is correct. I'm using haversine on the original WGS84 coordinates, which is fine.

Let me also check the output for a non-flagged transect that crosses -179... like T024.

T024: crosses_antimeridian_flag=false, lons=[-179.11, -176.83]
That's west of the antimeridian, so it doesn't actually cross it. The flag is false and it shouldn't cross. Good.

Let me check the output more carefully to make sure everything looks right.
4:01
4:01
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json

with open('/work/fiji_transects_fmg.geojson') as f:
    data = json.load(f)

# Check T001 (crosses antimeridian) in detail
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T001'][0]
coords = feat['geometry']['coordinates']
print(f'T001: {feat[\"geometry\"][\"type\"]}')
print(f'  Part 1 has {len(coords[0])} points')
print(f'  Part 2 has {len(coords[1])} points')
print(f'  Part 1 last point: {coords[0][-1]}')
print(f'  Part 2 first point: {coords[1][0]}')
print(f'  Difference: {abs(coords[0][-1][0]-coords[1][0][0])}, {abs(coords[0][-1][1]-coords[1][0][1])}')
print()

# Also verify T010
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T010'][0]
coords = feat['geometry']['coordinates']
print(f'T010: {feat[\"geometry\"][\"type\"]}')
print(f'  Parts: {len(coords)}')
for i, part in enumerate(coords):
    print(f'  Part {i+1}: {len(part)} points')
print()

# Check T025 (vertical line, doesn't cross)
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T025'][0]
coords = feat['geometry']['coordinates']
print(f'T025: {feat[\"geometry\"][\"type\"]}')
print(f'  Parts: {len(coords)}')
print(f'  Part 1: {len(coords[0])} points')
print(f'  length_m: {feat[\"properties\"][\"length_m\"]}')
"
T001: MultiLineString
  Part 1 has 4 points
  Part 2 has 4 points
  Part 1 last point: [2132641.3519591475, 3932030.6236885176]
  Part 2 first point: [2132641.3519591503, 3932030.6236885176]
  Difference: 2.7939677238464355e-09, 0.0

T010: MultiLineString
  Parts: 2
  Part 1: 3 points
  Part 2: 3 points

T025: MultiLineString
  Parts: 1
  Part 1: 4 points
  length_m: 108911.0
4:19
The parts connect correctly at the antimeridian. The difference at the connection point is at the nanometer level (2.8e-09 meters), so that's fine.

But wait - I should double-check my antimeridian splitting logic more carefully. For T001, the original coordinates are:

[ 178.94, -17.63 ], [ 179.46, -17.62 ], [ 179.97, -17.61 ], [ -179.51, -17.60 ], [ -179.00, -17.58 ], [ -178.48, -17.58 ]

So it goes from 179.97 to -179.51 crossing the antimeridian. In the split result:

Part 1: [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [180.0, lat_int]
Part 2: [-180.0, lat_int], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

That looks correct! The coordinates are projected into EPSG:3460 (Fiji Map Grid), so they should render correctly.

Now let me think about whether T010 is correctly split. T010 has:
[ 177.60, -17.28 ], [ 179.13, -17.47 ], [ -179.35, -17.65 ], [ -177.82, -17.84 ]

So it goes from 179.13 to -179.35 - that's crossing. Then from -179.35 to -177.82 - no crossing.

So T010 should be split into:
Part 1: [177.60, -17.28], [179.13, -17.47], [180.0, lat_int1]
Part 2: [-180.0, lat_int1], [-179.35, -17.65], [-177.82, -17.84]

That's 3 points in each part, which matches the output. Good.

Also, let me double-check T003:
[ 177.64, -17.36 ], [ 178.46, -17.42 ], [ 179.29, -17.47 ], [ -179.89, -17.53 ], [ -179.06, -17.59 ]

Going from 179.29 to -179.89 - crossing. Then -179.89 to -179.06 - no crossing.

Part 1: [177.64, -17.36], [178.46, -17.42], [179.29, -17.47], [180.0, lat_int]
Part 2: [-180.0, lat_int], [-179.89, -17.53], [-179.06, -17.59]

That's 4+3=7 points total (original had 5, we add 2 intersection points, so 7 points, split as 4+3). Let me check:
4:19
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json

with open('/work/fiji_transects_fmg.geojson') as f:
    data = json.load(f)

# Check T003 
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T003'][0]
coords = feat['geometry']['coordinates']
parts = len(coords)
ptotal = sum(len(c) for c in coords)
print(f'T003: {parts} parts, total {ptotal} projected points')
for i, c in enumerate(coords):
    print(f'  Part {i+1}: {len(c)} points')

# Check T004
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T004'][0]
coords = feat['geometry']['coordinates']
parts = len(coords)
ptotal = sum(len(c) for c in coords)
print(f'T004: {parts} parts, total {ptotal} projected points')
for i, c in enumerate(coords):
    print(f'  Part {i+1}: {len(c)} points')

# Check T010
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T010'][0]
coords = feat['geometry']['coordinates']
parts = len(coords)
ptotal = sum(len(c) for c in coords)
print(f'T010: {parts} parts, total {ptotal} projected points')
for i, c in enumerate(coords):
    print(f'  Part {i+1}: {len(c)} points')

# Check every feature is MultiLineString
all_ok = True
for feat in data['features']:
    if feat['geometry']['type'] != 'MultiLineString':
        print(f'ERROR: {feat[\"properties\"][\"transect_id\"]} is {feat[\"geometry\"][\"type\"]}')
        all_ok = False
if all_ok:
    print('All features are MultiLineString - OK')

# Check all required properties exist
for feat in data['features']:
    props = feat['properties']
    for key in ['transect_id', 'vessel', 'survey_date', 'length_m']:
        if key not in props:
            print(f'ERROR: {feat[\"properties\"].get(\"transect_id\",\"?\")} missing {key}')
print('Property check done')
"
T003: 2 parts, total 7 projected points
  Part 1: 4 points
  Part 2: 3 points
T004: 2 parts, total 8 projected points
  Part 1: 3 points
  Part 2: 5 points
T010: 2 parts, total 6 projected points
  Part 1: 3 points
  Part 2: 3 points
All features are MultiLineString - OK
Property check done
4:45
Everything looks good. Let me also double-check the original data to ensure T010's geometry makes sense - it has 4 original points and crosses the antimeridian.

T010 original coords: 
[ [ 177.602847396853463, -17.277026295424367 ],   (lon 177.6)
  [ 179.128053569323441, -17.468285296635063 ],   (lon 179.1)
  [ -179.346740258206609, -17.65419136085664 ],   (lon -179.3)
  [ -177.82153408573663, -17.837325774502055 ] ] (lon -177.8)

So it goes from 179.1 to -179.3 - that crosses the antimeridian. So it should be split into:
- Part 1: [177.6, 179.1, 180.0] (3 points)
- Part 2: [-180.0, -179.3, -177.8] (3 points)

Wait, part 2 has -180.0 as the intersection point but... actually the original coordinates go from 179.1 to -179.3. The interpolation at the antimeridian would be:
- We're going from lon=179.1 (east side) to lon=-179.3 (west side)
- Since 179.1 >= 0 and -179.3 < 0 and abs(179.1 - (-179.3)) = 358.4 > 180, it crosses
- Adjust lon2 = -179.3 + 360 = 180.7
- Target at 180: t = (180 - 179.1) / (180.7 - 179.1) = 0.9 / 1.6 = 0.5625
- Lat intersection: -17.4683 + 0.5625 * (-17.6542 - (-17.4683)) = -17.4683 + 0.5625 * (-0.1859) = -17.4683 - 0.1046 = -17.5729
- So the intersection is at (180, -17.5729) for the east side, and (-180, -17.5729) for the west side

This seems reasonable. And the output shows 3 points in part 1 and 3 points in part 2.

Let me also verify that the length is computed correctly. It uses haversine on the original WGS84 coords before splitting, so the length is the sum of great-circle distances between each consecutive point in the original, which is correct.

The antimeridian intersection points are the same point geographically, so in projected coordinates they should be nearly identical (as we saw with T001, difference of 2.8e-09 in easting which is nanometers - fine).

I think the solution is solid. Let me just make sure the output file is complete and valid.
4:45
4:45
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json

with open('/work/fiji_transects_fmg.geojson') as f:
    data = json.load(f)

# Validate GeoJSON structure
assert data['type'] == 'FeatureCollection'
assert data['name'] == 'fiji_transects_fmg'
assert data['crs']['properties']['name'] == 'urn:ogc:def:crs:EPSG::3460'

# Validate all features
ids_seen = set()
for feat in data['features']:
    tid = feat['properties']['transect_id']
    assert tid not in ids_seen, f'Duplicate {tid}'
    ids_seen.add(tid)
    
    assert feat['type'] == 'Feature'
    assert feat['geometry']['type'] == 'MultiLineString'
    coords = feat['geometry']['coordinates']
    assert isinstance(coords, list) and len(coords) >= 1
    
    props = feat['properties']
    assert 'vessel' in props
    assert 'survey_date' in props
    assert 'length_m' in props
    assert isinstance(props['length_m'], (int, float))
    
    # Each part should be a list of coordinate tuples
    for part in coords:
        assert isinstance(part, list) and len(part) >= 2
        for pt in part:
            assert len(pt) == 2
            # In Fiji Map Grid (EPSG:3460), eastings are ~1.8M-2.3M, northings ~3.9M-4.1M
            e, n = pt
            assert 1700000 < e < 2400000, f'{tid}: easting {e} out of range'
            assert 3700000 < n < 4200000, f'{tid}: northing {n} out of range'

print('All validations passed!')
print(f'Total features: {len(data[\"features\"])}')

# List all transect IDs and lengths
for feat in data['features']:
    tid = feat['properties']['transect_id']
    parts = len(feat['geometry']['coordinates'])
    length = feat['properties']['length_m']
    print(f'  {tid}: {parts} part(s), {length:.1f} m')
"
Traceback (most recent call last):
  File "<string>", line 37, in <module>
    assert 1700000 < e < 2400000, f'{tid}: easting {e} out of range'
           ^^^^^^^^^^^^^^^^^^^^^
AssertionError: T011: easting 2465344.860581143 out of range
5:00
T011 has easting ~2.46M which is over 2.4M. Let me adjust my bounds check. The Fiji Map Grid is a transverse Mercator with central meridian at 177°E, so the coordinates can vary. Let me just remove the strict bounds check or widen them.

Actually, the point is that T011 is entirely west of 180° (around -177° to -176° longitude). Let me check what the expected range is.

The Fiji Map Grid uses Fiji 1986 datum, central meridian at 177°E. Points at -177°E (or 183°E) would be about 6° from central meridian, so at the equator that's ~667km easting. But since we're at ~-18° latitude, it's a bit less. Hmm, 2.46M easting seems reasonable for a point around 177°W.

Let me just fix the validation bounds.
5:00
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json
with open('/work/fiji_transects_fmg.geojson') as f:
    data = json.load(f)

# Check coordinate ranges
min_e = min_n = float('inf')
max_e = max_n = float('-inf')
for feat in data['features']:
    for part in feat['geometry']['coordinates']:
        for pt in part:
            e, n = pt
            min_e = min(min_e, e)
            max_e = max(max_e, e)
            min_n = min(min_n, n)
            max_n = max(max_n, n)

print(f'Easting range: {min_e:.2f} to {max_e:.2f}')
print(f'Northing range: {min_n:.2f} to {max_n:.2f}')
"
Easting range: 1718720.30 to 2498125.93
Northing range: 3834700.43 to 4050623.35
5:17
The coordinate range check was too restrictive. The easting values are valid for EPSG:3460 (Fiji Map Grid). Some parts of Fiji extend further east. Let me verify EPSG:3460 bounds. From earlier: bounds: (176.81, -20.81, -178.15, -12.42). The projection covers all of Fiji. Let me just remove the overly restrictive assertion and run the validation again.
5:17
5:17
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json

with open('/work/fiji_transects_fmg.geojson') as f:
    data = json.load(f)

# Validate GeoJSON structure
assert data['type'] == 'FeatureCollection'
assert data['name'] == 'fiji_transects_fmg'
assert data['crs']['properties']['name'] == 'urn:ogc:def:crs:EPSG::3460'

# Validate all features
ids_seen = set()
for feat in data['features']:
    tid = feat['properties']['transect_id']
    assert tid not in ids_seen, f'Duplicate {tid}'
    ids_seen.add(tid)
    
    assert feat['type'] == 'Feature'
    assert feat['geometry']['type'] == 'MultiLineString'
    coords = feat['geometry']['coordinates']
    assert isinstance(coords, list) and len(coords) >= 1
    
    props = feat['properties']
    assert props.get('vessel') is not None
    assert props.get('survey_date') is not None
    assert isinstance(props.get('length_m'), (int, float))
    assert props['length_m'] > 0
    
    # Each part should have at least 2 points
    for part in coords:
        assert isinstance(part, list) and len(part) >= 2
        for pt in part:
            assert len(pt) == 2

print('All validations passed!')
print(f'Total features: {len(data[\"features\"])}')

# List all transect IDs and lengths
for feat in data['features']:
    tid = feat['properties']['transect_id']
    parts = len(feat['geometry']['coordinates'])
    length = feat['properties']['length_m']
    print(f'  {tid}: {parts} part(s), {length:.1f} m')
"
All validations passed!
Total features: 30
  T001: 2 part(s), 272883.6 m
  T002: 2 part(s), 216328.8 m
  T003: 2 part(s), 351329.4 m
  T004: 2 part(s), 372183.9 m
  T005: 2 part(s), 361918.2 m
  T006: 2 part(s), 393165.6 m
  T007: 2 part(s), 413939.2 m
  T008: 2 part(s), 146587.5 m
  T009: 2 part(s), 263036.0 m
  T010: 2 part(s), 489059.8 m
  T011: 1 part(s), 78224.7 m
  T012: 1 part(s), 84862.4 m
  T013: 1 part(s), 179453.7 m
  T014: 1 part(s), 148280.2 m
  T015: 1 part(s), 124401.4 m
  T016: 1 part(s), 53243.2 m
  T017: 1 part(s), 152593.7 m
  T018: 1 part(s), 112566.6 m
  T019: 1 part(s), 179201.5 m
  T020: 1 part(s), 215225.7 m
  T021: 1 part(s), 230359.8 m
  T022: 1 part(s), 51071.8 m
  T023: 1 part(s), 81789.9 m
  T024: 1 part(s), 250460.4 m
  T025: 1 part(s), 108911.0 m
  T026: 1 part(s), 100537.7 m
  T027: 1 part(s), 129918.7 m
  T028: 1 part(s), 172516.7 m
  T029: 1 part(s), 205033.9 m
  T030: 1 part(s), 163771.6 m
5:36
All validations passed. Let me also verify that the original WGS84 coordinates match between the input and what I'm using for my antimeridian splitting logic. Let me think about a subtle issue.

The problem says lines "render as arcs spanning the entire globe instead of short local segments". This is the classic antimeridian issue. When you have coordinates like [179.97, ...] and then [-179.51, ...], naive rendering connects them across the whole globe.

My solution:
1. Detects antimeridian crossing between consecutive points
2. Splits the line at the antimeridian by inserting an intersection point
3. Projects each part to Fiji Map Grid

But I need to think about whether my antimeridian intersection is correct. Let me trace through T001:

Original coords:
[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

The crossing happens between [179.97, -17.61] and [-179.51, -17.60].
prev=[179.97,-17.61], curr=[-179.51,-17.60]

Since prev[0]=179.97 >= 0 and curr[0]=-179.51 < 0, and abs(179.97 - (-179.51)) = 359.48 > 180, so crossing detected.

lon2_adj = -179.51 + 360 = 180.49
target_lon = 180.0
t = (180.0 - 179.97) / (180.49 - 179.97) = 0.03 / 0.52 ≈ 0.0577
lat_int = -17.61 + 0.0577 * (-17.60 - (-17.61)) = -17.61 + 0.0577 * 0.01 = -17.6094

So p_180 = (180.0, -17.6094) and p_neg180 = (-180.0, -17.6094)

Part 1: [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], (180.0, ~-17.609)
Part 2: (-180.0, ~-17.609), [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

That looks correct! The two parts meet at the antimeridian.

Now for projection, both (180.0, -17.609) and (-180.0, -17.609) should project to the same point in Fiji Map Grid (or very close). From the validation output, the difference was ~2.8e-09 which is just floating point noise. 

The output looks correct. Let me check that the file is complete and well-formed.
5:36
5:36
Bash cd /work && python3 -c "
cd /work && python3 -c "
import json

# Check GeoJSON is valid by re-parsing
with open('/work/fiji_transects_fmg.geojson') as f:
    content = f.read()
data = json.loads(content)

print(f'File size: {len(content)} bytes')
print(f'Feature count: {len(data[\"features\"])}')

# Check original attributes carried through
for feat in data['features']:
    p = feat['properties']
    # Compare with original
    pass

# Spot-check: T010 has 4 original points, should be 2 parts of 3 each (adding one intersection point per part)
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T010'][0]
parts = feat['geometry']['coordinates']
print(f'T010 has {len(parts)} parts')
for i, part in enumerate(parts):
    print(f'  Part {i+1}: {len(part)} points')
# Original: 4 points -> crossing between pt2 and pt3 -> split into 2 parts of 3 points each (adds 2 intersection points total)
print('T010 original had 4 points, crossing between pt2 and pt3 -> 2 parts of 3 coords each (correct)')

# Check T004: 6 original points, crossings?
feat = [f for f in data['features'] if f['properties']['transect_id'] == 'T004'][0]
parts = feat['geometry']['coordinates']
print(f'T004 has {len(parts)} parts')
for i, part in enumerate(parts):
    print(f'  Part {i+1}: {len(part)} points')

print('\\nAll checks passed!')
"
File size: 27640 bytes
Feature count: 30
T010 has 2 parts
  Part 1: 3 points
  Part 2: 3 points
T010 original had 4 points, crossing between pt2 and pt3 -> 2 parts of 3 coords each (correct)
T004 has 2 parts
  Part 1: 3 points
  Part 2: 5 points

All checks passed!
6:10
The solution looks good. Let me verify T004's original coordinates to ensure the split is correct:

T004 original coordinates:
1. [178.873696381462679, -17.803295238757038]
2. [179.542254331047587, -17.598477321703708]
3. [-179.789187719367533, -17.405549472537043]
4. [-179.120629769782624, -17.19411411231702]
5. [-178.452071820197745, -16.992332922174427]
6. [-177.783513870612836, -16.795171196845544]

The antimeridian crossing is between point 2 (179.54) and point 3 (-179.79). So:
- Part 1: points 1, 2, intersection at 180°
- Part 2: intersection at -180°, points 3, 4, 5, 6

Part 1 has 3 points (original p1, p2, intersection) ✓
Part 2 has 5 points (intersection, p3, p4, p5, p6) ✓

Wait, actually T004 original has 6 points. After crossing between pt2 and pt3:
- Part 1: p1, p2, intersection_at_180 → 3 points
- Part 2: intersection_at_-180, p3, p4, p5, p6 → 5 points

That matches what we have. Good.

Everything looks solid. Let me provide a summary.
6:10