Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions GEOLIBRE_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@ model-builder.md, segmentation.md, obia.md_
spatial join, clip, erase, split, intersect, identity, union, symmetrical difference, update
and near are built in.
- [ ] **More built-in vector tools**: attribute join, extract vertices, points along lines,
fix/check geometry, random extract, buffer options. Partial: Merge (append layers) and extract vertices are built in,
as Merge and Feature Vertices To Points (_Roadmap: solid ground_).
fix/check geometry, random extract, buffer options. Partial: Merge (append layers), extract
vertices and points along lines are built in, as Merge, Feature Vertices To Points and Points
Along Lines (_Roadmap: solid ground_).
- [ ] **Routing services**: isochrones, service areas, OD matrix, multi-stop routing,
turn-by-turn (Valhalla/OSRM). Partial: shortest path over a line layer (_Roadmap: analysis_).
- [ ] **Geocoding**: forward, batch and reverse, with multiple providers and a place search box.
Expand Down
13 changes: 7 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Correct, robust and recoverable, with the foundations other themes stand on.
administrator can fix for a managed install (no AI Assistant, plugins or web services).
- Extend the OS keychain secret store to service connections (database passwords, S3 keys,
AI provider keys and the ChatGPT sign-in's tokens are already kept there).
- Vector tools, each writing a new layer: points along lines, check and fix geometry _(open)_.
- Vector tools, each writing a new layer: check and fix geometry _(open)_.
- Batch runs: one tool over many layers or a folder of files.

### Expressions and cartography
Expand Down Expand Up @@ -224,11 +224,12 @@ Open questions rather than plans; discuss before building.
(buffer, near) and summary tools, pivot tables, tabulate intersection (class areas and totals
shared out to zones by overlap), spatial join by intersection, centre, largest
overlap, closest and distance with merge rules, join field (non-spatial attribute join), feature
vertices to points, zonal statistics, kernel density, A5 grids, kriging, Calculate Field with an
expression language, Calculate Geometry with metric or imperial units, Project Raster, and
vector and raster PMTiles export (#557); guided workflows (viewshed, line of sight, IDW, shortest
path, suitability); ModelBuilder on a node canvas, scripts with command, parameter and value completion,
and history copied as code; the GeoLibre and Whitebox suite (1,000+ tools) natively and in the browser.
vertices to points, points along lines, zonal statistics, kernel density, A5 grids, kriging,
Calculate Field with an expression language, Calculate Geometry with metric or imperial units,
Project Raster, and vector and raster PMTiles export (#557); guided workflows (viewshed, line of
sight, IDW, shortest path, suitability); ModelBuilder on a node canvas, scripts with command,
parameter and value completion, and history copied as code; the GeoLibre and Whitebox suite
(1,000+ tools) natively and in the browser.
- Layouts (map frames, surrounds, map series, publication sheets and figures in one command,
format panes, guides, snapping, element locking and order, measured grids and PDF), reports,
presentations and
Expand Down
54 changes: 49 additions & 5 deletions crates/analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,25 +367,69 @@ pub fn resolve_params(spec: &ToolSpec, params: &Value) -> Result<Value, String>
{
*v = choice(spec, ps, choices, v)?;
}
if ps.kind == ParamKind::Bool
&& built_in
&& let Some(v) = p.get_mut(ps.name)
{
*v = boolean(spec, ps, v)?;
}
}
Ok(Value::Object(p))
}

/// A built-in tool's on-off parameter as `true` or `false` (text saying so counts). Anything else is
/// an error, never a guess at what `"yes"` or `1` meant (#406). A blank value, or null, is the
/// default.
fn boolean(spec: &ToolSpec, ps: &ParamSpec, v: &Value) -> Result<Value, String> {
let default = || serde_json::from_str(ps.default).unwrap_or(Value::Null);
match v {
Value::Bool(_) => Ok(v.clone()),
Value::Null => Ok(default()),
Value::String(s) if s.trim().is_empty() => Ok(default()),
Value::String(s) if s.trim().eq_ignore_ascii_case("true") => Ok(Value::Bool(true)),
Value::String(s) if s.trim().eq_ignore_ascii_case("false") => Ok(Value::Bool(false)),
_ => Err(format!("{}: '{}' must be true or false (not {})", spec.name, ps.label, shown(v))),
}
}

/// A refused value as JSON for its message: up to 80 characters, then "…". A value can be huge,
/// and is written out only that far (serde_json stops when its writer does).
fn shown(v: &Value) -> String {
struct Upto(String, usize);
impl std::fmt::Write for Upto {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
for c in s.chars() {
if self.1 == 0 {
return Err(std::fmt::Error);
}
self.0.push(c);
self.1 -= 1;
}
Ok(())
}
}
let mut out = Upto(String::new(), 80);
if std::fmt::write(&mut out, format_args!("{v}")).is_err() {
out.0.push('…');
}
out.0
}

/// A built-in tool's choice parameter as one of its choices, spelled as declared (`"meters"` is
/// `"Meters"`). Any other value is an error naming the choices: a tool never guesses what an
/// unknown one meant (#406). A blank value is the declared default.
fn choice(spec: &ToolSpec, ps: &ParamSpec, choices: &[&str], v: &Value) -> Result<Value, String> {
let given = match v {
let given: std::borrow::Cow<'_, str> = match v {
Value::Null => return Ok(Value::Null),
// Blank: the declared default, as when it's left out.
Value::String(s) if s.trim().is_empty() => return Ok(serde_json::from_str(ps.default).unwrap_or_else(|_| v.clone())),
Value::String(s) => s.trim().to_string(),
Value::Number(_) | Value::Bool(_) => v.to_string(),
_ => String::new(),
Value::String(s) => s.trim().into(),
Value::Number(_) | Value::Bool(_) => v.to_string().into(),
_ => "".into(),
};
match choices.iter().find(|c| c.eq_ignore_ascii_case(&given)) {
Some(c) => Ok(Value::String((*c).to_string())),
None => Err(format!("{}: '{}' must be one of {} (not {v})", spec.name, ps.label, choices.join(", "))),
None => Err(format!("{}: '{}' must be one of {} (not {})", spec.name, ps.label, choices.join(", "), shown(v))),
}
}

Expand Down
Loading
Loading