1use std::collections::{HashMap, HashSet};
17use std::path::Path;
18use crate::error::{GeoError, Result};
19use crate::feature::{FieldDef, FieldType, FieldValue, Feature, Layer, Schema};
20use crate::geometry::{Coord, Geometry, Ring};
21use crate::reproject;
22
23pub fn read<P: AsRef<Path>>(path: P) -> Result<Layer> {
29 let text = std::fs::read_to_string(path).map_err(GeoError::Io)?;
30 parse_str(&text)
31}
32
33pub fn parse_str(text: &str) -> Result<Layer> {
35 let val = Parser::new(text).parse_value()?;
36 layer_from_value(val, "layer")
37}
38
39pub fn write<P: AsRef<Path>>(layer: &Layer, path: P) -> Result<()> {
41 let out_layer = prepare_rfc7946_layer(layer)?;
42 std::fs::write(path, to_string(&out_layer).as_bytes()).map_err(GeoError::Io)
43}
44
45pub fn to_string(layer: &Layer) -> String {
47 let mut s = String::new();
48 write_feature_collection(&mut s, layer);
49 s
50}
51
52fn prepare_rfc7946_layer(layer: &Layer) -> Result<Layer> {
53 if layer.crs_epsg() == Some(4326) {
56 return Ok(layer.clone());
57 }
58
59 if layer.crs_epsg().is_some() || layer.crs_wkt().is_some() {
60 return reproject::layer_to_epsg(layer, 4326);
61 }
62
63 Ok(layer.clone())
64}
65
66#[derive(Debug, Clone)]
71enum Jv {
72 Null,
73 Bool(bool),
74 Num(f64),
75 Str(String),
76 Arr(Vec<Jv>),
77 Obj(Vec<(String, Jv)>), }
79
80impl Jv {
81 fn get(&self, key: &str) -> Option<&Jv> {
82 if let Jv::Obj(pairs) = self { pairs.iter().find(|(k,_)| k == key).map(|(_,v)| v) }
83 else { None }
84 }
85 fn as_str(&self) -> Option<&str> { if let Jv::Str(s) = self { Some(s) } else { None } }
86 fn as_f64(&self) -> Option<f64> { if let Jv::Num(n) = self { Some(*n) } else { None } }
87 fn as_arr(&self) -> Option<&[Jv]> { if let Jv::Arr(a) = self { Some(a) } else { None } }
88}
89
90struct Parser<'a> {
95 src: &'a [u8],
96 pos: usize,
97}
98
99impl<'a> Parser<'a> {
100 fn new(s: &'a str) -> Self { Self { src: s.as_bytes(), pos: 0 } }
101
102 fn err(&self, msg: &str) -> GeoError {
103 GeoError::GeoJsonParse { offset: self.pos, msg: msg.to_owned() }
104 }
105
106 fn peek(&self) -> Option<u8> { self.src.get(self.pos).copied() }
107
108 fn skip_ws(&mut self) {
109 while matches!(self.peek(), Some(b' '|b'\t'|b'\n'|b'\r')) { self.pos += 1; }
110 }
111
112 fn eat(&mut self, b: u8) -> Result<()> {
113 self.skip_ws();
114 if self.peek() == Some(b) { self.pos += 1; Ok(()) }
115 else { Err(self.err(&format!("expected '{}' got {:?}", b as char, self.peek().map(|b| b as char)))) }
116 }
117
118 pub fn parse_value(&mut self) -> Result<Jv> {
119 self.skip_ws();
120 match self.peek() {
121 Some(b'"') => self.parse_string().map(Jv::Str),
122 Some(b'{') => self.parse_object(),
123 Some(b'[') => self.parse_array(),
124 Some(b't') => { self.pos += 4; Ok(Jv::Bool(true)) }
125 Some(b'f') => { self.pos += 5; Ok(Jv::Bool(false)) }
126 Some(b'n') => { self.pos += 4; Ok(Jv::Null) }
127 Some(b'-') | Some(b'0'..=b'9') => self.parse_number(),
128 Some(b) => Err(self.err(&format!("unexpected byte 0x{b:02X}"))),
129 None => Err(self.err("unexpected end of input")),
130 }
131 }
132
133 fn parse_string(&mut self) -> Result<String> {
134 self.eat(b'"')?;
135 let mut s = String::new();
136 loop {
137 match self.peek() {
138 None => return Err(self.err("unterminated string")),
139 Some(b'"') => { self.pos += 1; break; }
140 Some(b'\\') => {
141 self.pos += 1;
142 match self.peek() {
143 Some(b'"') => { s.push('"'); self.pos += 1; }
144 Some(b'\\') => { s.push('\\'); self.pos += 1; }
145 Some(b'/') => { s.push('/'); self.pos += 1; }
146 Some(b'n') => { s.push('\n'); self.pos += 1; }
147 Some(b'r') => { s.push('\r'); self.pos += 1; }
148 Some(b't') => { s.push('\t'); self.pos += 1; }
149 Some(b'b') => { s.push('\x08'); self.pos += 1; }
150 Some(b'f') => { s.push('\x0C'); self.pos += 1; }
151 Some(b'u') => {
152 self.pos += 1;
153 if self.pos + 4 > self.src.len() {
154 return Err(self.err("truncated \\u escape"));
155 }
156 let hex = std::str::from_utf8(&self.src[self.pos..self.pos+4])
157 .map_err(|_| self.err("invalid \\u escape"))?;
158 let cp = u32::from_str_radix(hex, 16)
159 .map_err(|_| self.err("invalid \\u codepoint"))?;
160 if let Some(ch) = char::from_u32(cp) { s.push(ch); }
161 self.pos += 4;
162 }
163 _ => s.push('\\'),
164 }
165 }
166 Some(b) => { s.push(b as char); self.pos += 1; }
167 }
168 }
169 Ok(s)
170 }
171
172 fn parse_number(&mut self) -> Result<Jv> {
173 let start = self.pos;
174 if self.peek() == Some(b'-') { self.pos += 1; }
175 while matches!(self.peek(), Some(b'0'..=b'9')) { self.pos += 1; }
176 if self.peek() == Some(b'.') {
177 self.pos += 1;
178 while matches!(self.peek(), Some(b'0'..=b'9')) { self.pos += 1; }
179 }
180 if matches!(self.peek(), Some(b'e'|b'E')) {
181 self.pos += 1;
182 if matches!(self.peek(), Some(b'+'|b'-')) { self.pos += 1; }
183 while matches!(self.peek(), Some(b'0'..=b'9')) { self.pos += 1; }
184 }
185 let s = std::str::from_utf8(&self.src[start..self.pos])
186 .map_err(|_| self.err("invalid number bytes"))?;
187 let n: f64 = s.parse().map_err(|_| self.err("invalid number"))?;
188 Ok(Jv::Num(n))
189 }
190
191 fn parse_array(&mut self) -> Result<Jv> {
192 self.eat(b'[')?;
193 let mut arr = Vec::new();
194 self.skip_ws();
195 if self.peek() == Some(b']') { self.pos += 1; return Ok(Jv::Arr(arr)); }
196 loop {
197 arr.push(self.parse_value()?);
198 self.skip_ws();
199 match self.peek() {
200 Some(b',') => { self.pos += 1; }
201 Some(b']') => { self.pos += 1; break; }
202 _ => return Err(self.err("expected ',' or ']'")),
203 }
204 }
205 Ok(Jv::Arr(arr))
206 }
207
208 fn parse_object(&mut self) -> Result<Jv> {
209 self.eat(b'{')?;
210 let mut pairs: Vec<(String, Jv)> = Vec::new();
211 self.skip_ws();
212 if self.peek() == Some(b'}') { self.pos += 1; return Ok(Jv::Obj(pairs)); }
213 loop {
214 self.skip_ws();
215 let key = self.parse_string()?;
216 self.eat(b':')?;
217 let val = self.parse_value()?;
218 pairs.push((key, val));
219 self.skip_ws();
220 match self.peek() {
221 Some(b',') => { self.pos += 1; }
222 Some(b'}') => { self.pos += 1; break; }
223 _ => return Err(self.err("expected ',' or '}'")),
224 }
225 }
226 Ok(Jv::Obj(pairs))
227 }
228}
229
230fn layer_from_value(val: Jv, name: &str) -> Result<Layer> {
235 let type_s = val.get("type").and_then(|v| v.as_str()).unwrap_or("").to_owned();
236 match type_s.as_str() {
237 "FeatureCollection" => parse_feature_collection(val, name),
238 "Feature" => {
239 let mut layer = Layer::new(name);
240 if let Some(f) = build_feature(&val, &layer.schema, 0)? {
241 if let Some(geom) = &f.geometry {
242 layer.geom_type = Some(geom.geom_type());
243 }
244 layer.push(f);
245 }
246 Ok(layer)
247 }
248 _ => {
249 let geom = parse_geometry(&val)?;
251 let mut layer = Layer::new(name);
252 layer.geom_type = Some(geom.geom_type());
253 layer.push(Feature { fid: 0, geometry: Some(geom), attributes: vec![] });
254 Ok(layer)
255 }
256 }
257}
258
259fn parse_feature_collection(val: Jv, name: &str) -> Result<Layer> {
260 let features_arr = val.get("features").and_then(|v| v.as_arr())
261 .ok_or_else(|| GeoError::GeoJsonMissing("features".into()))?;
262
263 let mut key_order: Vec<String> = Vec::new();
265 let mut key_seen: HashSet<String> = HashSet::new();
266 let mut key_type: HashMap<String, FieldType> = HashMap::new();
267
268 for feat in features_arr {
269 if let Some(Jv::Obj(props)) = feat.get("properties") {
270 for (k, v) in props {
271 if key_seen.insert(k.clone()) { key_order.push(k.clone()); }
272 if matches!(v, Jv::Null) { continue; }
273 let inferred = infer_type(v);
274 let entry = key_type.entry(k.clone()).or_insert(inferred);
275 *entry = FieldValue::widen_type(*entry, inferred);
276 }
277 }
278 }
279
280 let mut layer = Layer::new(name);
281 for k in &key_order {
282 let ft = key_type.get(k).copied().unwrap_or(FieldType::Text);
283 layer.add_field(FieldDef::new(k, ft));
284 }
285
286 for (idx, feat_val) in features_arr.iter().enumerate() {
287 if let Some(f) = build_feature(feat_val, &layer.schema, idx as u64)? {
288 if layer.geom_type.is_none() {
290 if let Some(geom) = &f.geometry {
291 layer.geom_type = Some(geom.geom_type());
292 }
293 }
294 layer.push(f);
295 }
296 }
297
298 Ok(layer)
299}
300
301fn infer_type(v: &Jv) -> FieldType {
302 match v {
303 Jv::Bool(_) => FieldType::Boolean,
304 Jv::Num(n) => if n.fract() == 0.0 { FieldType::Integer } else { FieldType::Float },
305 Jv::Null => FieldType::Text, Jv::Arr(_) | Jv::Obj(_) => FieldType::Json,
307 Jv::Str(s) => if looks_like_date(s) { FieldType::Date } else { FieldType::Text },
308 }
309}
310
311fn looks_like_date(s: &str) -> bool {
312 let b = s.as_bytes();
313 b.len() == 10 && b[4] == b'-' && b[7] == b'-'
314}
315
316fn build_feature(val: &Jv, schema: &Schema, fid: u64) -> Result<Option<Feature>> {
317 let type_s = val.get("type").and_then(|v| v.as_str()).unwrap_or("");
318 if type_s != "Feature" {
319 return Err(GeoError::GeoJsonType(type_s.to_owned()));
320 }
321
322 let geom = match val.get("geometry") {
323 Some(Jv::Null) | None => None,
324 Some(g) => Some(parse_geometry(g)?),
325 };
326
327 let mut attrs = vec![FieldValue::Null; schema.len()];
328 if let Some(Jv::Obj(props)) = val.get("properties") {
329 for (k, v) in props {
330 if let Some(idx) = schema.field_index(k) {
331 let ft = schema.fields()[idx].field_type;
332 attrs[idx] = jv_to_field(v, ft);
333 }
334 }
335 }
336
337 Ok(Some(Feature { fid, geometry: geom, attributes: attrs }))
338}
339
340fn jv_to_field(v: &Jv, ft: FieldType) -> FieldValue {
341 match (v, ft) {
342 (Jv::Null, _) => FieldValue::Null,
343 (Jv::Bool(b), _) => FieldValue::Boolean(*b),
344 (Jv::Num(n), FieldType::Integer) => FieldValue::Integer(*n as i64),
345 (Jv::Num(n), _) => FieldValue::Float(*n),
346 (Jv::Str(s), FieldType::Date) => FieldValue::Date(s.clone()),
347 (Jv::Str(s), FieldType::DateTime) => FieldValue::DateTime(s.clone()),
348 (Jv::Str(s), _) => FieldValue::Text(s.clone()),
349 (Jv::Arr(_), _) | (Jv::Obj(_), _) => FieldValue::Text(jv_to_json_str(v)),
350 }
351}
352
353fn jv_to_json_str(v: &Jv) -> String {
354 match v {
355 Jv::Null => "null".into(),
356 Jv::Bool(b) => b.to_string(),
357 Jv::Num(n) => fmt_number(*n),
358 Jv::Str(s) => format!("\"{}\"", s.replace('"', "\\\"")),
359 Jv::Arr(a) => format!("[{}]", a.iter().map(jv_to_json_str).collect::<Vec<_>>().join(",")),
360 Jv::Obj(o) => {
361 let pairs: Vec<String> = o.iter().map(|(k,v)| format!("\"{}\":{}", k, jv_to_json_str(v))).collect();
362 format!("{{{}}}", pairs.join(","))
363 }
364 }
365}
366
367fn parse_geometry(val: &Jv) -> Result<Geometry> {
372 let type_s = val.get("type").and_then(|v| v.as_str())
373 .ok_or_else(|| GeoError::GeoJsonMissing("geometry.type".into()))?;
374 let coords = val.get("coordinates");
375
376 match type_s {
377 "Point" => {
378 let c = parse_one_coord(coords.ok_or_else(|| GeoError::GeoJsonMissing("coordinates".into()))?)?;
379 Ok(Geometry::Point(c))
380 }
381 "LineString" => {
382 let cs = parse_coord_ring(coords.ok_or_else(|| GeoError::GeoJsonMissing("coordinates".into()))?)?;
383 Ok(Geometry::LineString(cs))
384 }
385 "Polygon" => {
386 let rings = coords.and_then(|v| v.as_arr())
387 .ok_or_else(|| GeoError::GeoJsonMissing("polygon coordinates".into()))?;
388 let mut parsed: Vec<Vec<Coord>> = rings.iter()
389 .map(|r| parse_coord_ring(r).map(strip_closed_ring))
390 .collect::<Result<_>>()?;
391 let exterior = parsed.drain(..1).next().unwrap_or_default();
392 Ok(Geometry::polygon(exterior, parsed))
393 }
394 "MultiPoint" => {
395 let cs = parse_coord_ring(coords.ok_or_else(|| GeoError::GeoJsonMissing("coordinates".into()))?)?;
396 Ok(Geometry::MultiPoint(cs))
397 }
398 "MultiLineString" => {
399 let lines = coords.and_then(|v| v.as_arr())
400 .ok_or_else(|| GeoError::GeoJsonMissing("MultiLineString coordinates".into()))?;
401 let ls: Vec<Vec<Coord>> = lines.iter().map(|l| parse_coord_ring(l)).collect::<Result<_>>()?;
402 Ok(Geometry::MultiLineString(ls))
403 }
404 "MultiPolygon" => {
405 let polys = coords.and_then(|v| v.as_arr())
406 .ok_or_else(|| GeoError::GeoJsonMissing("MultiPolygon coordinates".into()))?;
407 let ps: Vec<(Vec<Coord>, Vec<Vec<Coord>>)> = polys.iter().map(|poly| {
408 let rings = poly.as_arr().ok_or_else(|| GeoError::GeoJsonMissing("polygon rings".into()))?;
409 let mut parsed: Vec<Vec<Coord>> = rings.iter()
410 .map(|r| parse_coord_ring(r).map(strip_closed_ring))
411 .collect::<Result<_>>()?;
412 let ext = parsed.drain(..1).next().unwrap_or_default();
413 Ok((ext, parsed))
414 }).collect::<Result<_>>()?;
415 Ok(Geometry::multi_polygon(ps))
416 }
417 "GeometryCollection" => {
418 let geoms = val.get("geometries").and_then(|v| v.as_arr())
419 .ok_or_else(|| GeoError::GeoJsonMissing("geometries".into()))?;
420 let gs: Vec<Geometry> = geoms.iter().map(|g| parse_geometry(g)).collect::<Result<_>>()?;
421 Ok(Geometry::GeometryCollection(gs))
422 }
423 other => Err(GeoError::GeoJsonType(other.to_owned())),
424 }
425}
426
427fn parse_one_coord(v: &Jv) -> Result<Coord> {
428 let a = v.as_arr().ok_or_else(|| GeoError::GeoJsonParse { offset: 0, msg: "coordinate must be array".into() })?;
429 let x = a.get(0).and_then(|v| v.as_f64()).ok_or_else(|| GeoError::GeoJsonParse { offset: 0, msg: "missing x".into() })?;
430 let y = a.get(1).and_then(|v| v.as_f64()).ok_or_else(|| GeoError::GeoJsonParse { offset: 0, msg: "missing y".into() })?;
431 let z = a.get(2).and_then(|v| v.as_f64());
432 Ok(Coord { x, y, z, m: None })
433}
434
435fn parse_coord_ring(v: &Jv) -> Result<Vec<Coord>> {
436 let arr = v.as_arr().ok_or_else(|| GeoError::GeoJsonParse { offset: 0, msg: "expected coord array".into() })?;
437 arr.iter().map(|c| parse_one_coord(c)).collect()
438}
439
440fn strip_closed_ring(mut coords: Vec<Coord>) -> Vec<Coord> {
441 if coords.len() > 1 {
442 let first = coords.first().cloned();
443 let last = coords.last().cloned();
444 if first == last {
445 coords.pop();
446 }
447 }
448 coords
449}
450
451fn write_feature_collection(s: &mut String, layer: &Layer) {
456 s.push_str(r#"{"type":"FeatureCollection","features":["#);
457 for (i, f) in layer.features.iter().enumerate() {
458 if i > 0 { s.push(','); }
459 write_feature(s, f, &layer.schema);
460 }
461 s.push_str("]}");
462}
463
464fn write_feature(s: &mut String, f: &Feature, schema: &Schema) {
465 s.push_str(r#"{"type":"Feature","geometry":"#);
466 match &f.geometry { None => s.push_str("null"), Some(g) => write_geom(s, g) }
467 s.push_str(r#","properties":"#);
468 write_props(s, f, schema);
469 s.push('}');
470}
471
472fn write_geom(s: &mut String, g: &Geometry) {
473 match g {
474 Geometry::Point(c) => {
475 s.push_str(r#"{"type":"Point","coordinates":"#);
476 write_coord(s, c); s.push('}');
477 }
478 Geometry::LineString(cs) => {
479 s.push_str(r#"{"type":"LineString","coordinates":"#);
480 write_coord_arr(s, cs); s.push('}');
481 }
482 Geometry::Polygon { exterior, interiors } => {
483 s.push_str(r#"{"type":"Polygon","coordinates":["#);
484 write_ring_arr(s, exterior);
485 for r in interiors { s.push(','); write_ring_arr(s, r); }
486 s.push_str("]}");
487 }
488 Geometry::MultiPoint(cs) => {
489 s.push_str(r#"{"type":"MultiPoint","coordinates":"#);
490 write_coord_arr(s, cs); s.push('}');
491 }
492 Geometry::MultiLineString(ls) => {
493 s.push_str(r#"{"type":"MultiLineString","coordinates":["#);
494 for (i, l) in ls.iter().enumerate() { if i>0 {s.push(',');} write_coord_arr(s, l); }
495 s.push_str("]}");
496 }
497 Geometry::MultiPolygon(ps) => {
498 s.push_str(r#"{"type":"MultiPolygon","coordinates":["#);
499 for (i, (e, hs)) in ps.iter().enumerate() {
500 if i>0 {s.push(',');} s.push('[');
501 write_ring_arr(s, e);
502 for h in hs { s.push(','); write_ring_arr(s, h); }
503 s.push(']');
504 }
505 s.push_str("]}");
506 }
507 Geometry::GeometryCollection(gs) => {
508 s.push_str(r#"{"type":"GeometryCollection","geometries":["#);
509 for (i, g) in gs.iter().enumerate() { if i>0 {s.push(',');} write_geom(s, g); }
510 s.push_str("]}");
511 }
512 }
513}
514
515fn write_coord(s: &mut String, c: &Coord) {
516 s.push('[');
517 s.push_str(&fmt_number(c.x)); s.push(','); s.push_str(&fmt_number(c.y));
518 if let Some(z) = c.z { s.push(','); s.push_str(&fmt_number(z)); }
519 s.push(']');
520}
521
522fn write_coord_arr(s: &mut String, cs: &[Coord]) {
523 s.push('[');
524 for (i, c) in cs.iter().enumerate() { if i>0 {s.push(',');} write_coord(s, c); }
525 s.push(']');
526}
527
528fn write_ring_arr(s: &mut String, ring: &Ring) {
529 s.push('[');
530 for (i, c) in ring.0.iter().enumerate() { if i>0 {s.push(',');} write_coord(s, c); }
531 if !ring.0.is_empty() { s.push(','); write_coord(s, &ring.0[0]); }
533 s.push(']');
534}
535
536fn write_props(s: &mut String, f: &Feature, schema: &Schema) {
537 if schema.is_empty() { s.push_str("null"); return; }
538 s.push('{');
539 let mut first = true;
540 for (i, fd) in schema.fields().iter().enumerate() {
541 if !first { s.push(','); }
542 first = false;
543 write_json_str(s, &fd.name);
544 s.push(':');
545 let val = f.attributes.get(i).unwrap_or(&FieldValue::Null);
546 write_field_value(s, val);
547 }
548 s.push('}');
549}
550
551fn write_field_value(s: &mut String, val: &FieldValue) {
552 match val {
553 FieldValue::Null => s.push_str("null"),
554 FieldValue::Integer(v) => s.push_str(&v.to_string()),
555 FieldValue::Float(v) => s.push_str(&fmt_number(*v)),
556 FieldValue::Boolean(v) => s.push_str(if *v { "true" } else { "false" }),
557 FieldValue::Text(v) | FieldValue::Date(v) | FieldValue::DateTime(v) => write_json_str(s, v),
558 FieldValue::Blob(b) => {
559 s.push('"');
561 for byte in b { s.push_str(&format!("{byte:02X}")); }
562 s.push('"');
563 }
564 }
565}
566
567fn write_json_str(s: &mut String, v: &str) {
568 s.push('"');
569 for ch in v.chars() {
570 match ch {
571 '"' => s.push_str("\\\""),
572 '\\' => s.push_str("\\\\"),
573 '\n' => s.push_str("\\n"),
574 '\r' => s.push_str("\\r"),
575 '\t' => s.push_str("\\t"),
576 c => s.push(c),
577 }
578 }
579 s.push('"');
580}
581
582fn fmt_number(n: f64) -> String {
583 if n.fract() == 0.0 && n.abs() < 1e15 { format!("{}", n as i64) }
584 else { format!("{n}") }
585}
586
587#[cfg(test)]
592mod tests {
593 use super::*;
594
595 const SAMPLE: &str = r#"{
596 "type": "FeatureCollection",
597 "features": [
598 {"type":"Feature",
599 "geometry":{"type":"Point","coordinates":[10.5,20.0]},
600 "properties":{"name":"alpha","count":7,"score":3.14}},
601 {"type":"Feature",
602 "geometry":{"type":"Polygon","coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]},
603 "properties":{"name":"beta","count":2,"score":null}}
604 ]
605 }"#;
606
607 #[test]
608 fn parse_fc() {
609 let l = parse_str(SAMPLE).unwrap();
610 assert_eq!(l.len(), 2);
611 assert_eq!(l.schema.len(), 3);
612 }
613
614 #[test]
615 fn parse_point() {
616 let l = parse_str(SAMPLE).unwrap();
617 if let Some(Geometry::Point(c)) = &l[0].geometry {
618 assert!((c.x - 10.5).abs() < 1e-9);
619 assert!((c.y - 20.0).abs() < 1e-9);
620 } else { panic!("expected Point"); }
621 }
622
623 #[test]
624 fn parse_polygon() {
625 let l = parse_str(SAMPLE).unwrap();
626 if let Some(Geometry::Polygon { exterior, interiors }) = &l[1].geometry {
627 assert_eq!(exterior.len(), 4); assert!(interiors.is_empty());
629 } else { panic!("expected Polygon"); }
630 }
631
632 #[test]
633 fn field_types() {
634 let l = parse_str(SAMPLE).unwrap();
635 let f = l.schema.field("name").unwrap();
636 assert_eq!(f.field_type, FieldType::Text);
637 let f = l.schema.field("count").unwrap();
638 assert_eq!(f.field_type, FieldType::Integer);
639 let f = l.schema.field("score").unwrap();
640 assert_eq!(f.field_type, FieldType::Float);
642 }
643
644 #[test]
645 fn roundtrip() {
646 let l1 = parse_str(SAMPLE).unwrap();
647 let json = to_string(&l1);
648 let l2 = parse_str(&json).unwrap();
649 assert_eq!(l1.len(), l2.len());
650 assert_eq!(l1.schema.len(), l2.schema.len());
651 }
652
653 #[test]
654 fn null_geometry() {
655 let text = r#"{"type":"FeatureCollection","features":[
656 {"type":"Feature","geometry":null,"properties":{"id":1}}]}"#;
657 let l = parse_str(text).unwrap();
658 assert!(l[0].geometry.is_none());
659 }
660
661 #[test]
662 fn geometry_collection() {
663 let text = r#"{"type":"GeometryCollection","geometries":[
664 {"type":"Point","coordinates":[0,0]},
665 {"type":"LineString","coordinates":[[0,0],[1,1]]}]}"#;
666 let l = parse_str(text).unwrap();
667 assert!(matches!(l[0].geometry, Some(Geometry::GeometryCollection(_))));
668 }
669
670 #[test]
671 fn file_roundtrip() {
672 let dir = tempfile::tempdir().unwrap();
673 let path = dir.path().join("test.geojson");
674 let l1 = parse_str(SAMPLE).unwrap();
675 write(&l1, &path).unwrap();
676 let l2 = read(&path).unwrap();
677 assert_eq!(l2.len(), 2);
678 }
679
680 #[test]
681 fn write_reprojects_projected_layer_to_epsg4326() {
682 let dir = tempfile::tempdir().unwrap();
683 let path = dir.path().join("reprojected.geojson");
684
685 let mut layer = Layer::new("mercator").with_crs_epsg(3857);
686 layer.push(Feature {
687 fid: 0,
688 geometry: Some(Geometry::point(111319.49079327357, 0.0)),
689 attributes: vec![],
690 });
691
692 write(&layer, &path).unwrap();
693 let out = read(&path).unwrap();
694
695 if let Some(Geometry::Point(c)) = &out[0].geometry {
696 assert!((c.x - 1.0).abs() < 1.0e-5);
697 assert!(c.y.abs() < 1.0e-9);
698 } else {
699 panic!("expected Point geometry");
700 }
701 }
702}