Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6ceac40
Plan: survey area of interest (AOI) and geoDistanceToAOIBoundary expr…
SteRiccio Oct 28, 2026
d46bb63
Survey area of interest (AOI) and geoDistanceToAOIBoundary expression…
SteRiccio Oct 28, 2026
2341a3f
Changelog and What's new: area of interest, credit to ASSEDEV
SteRiccio Oct 28, 2026
14504e1
Bump @openforis/arena-core to 2.13.1 (area of interest read from surv…
SteRiccio Oct 28, 2026
39b042d
Coordinate attribute: store accuracy, altitude and altitude accuracy …
SteRiccio Oct 28, 2026
351016d
E2E: area of interest upload and location status in data entry
SteRiccio Oct 28, 2026
723e076
What's new: area of interest screenshot, ASSEDEV full name
SteRiccio Oct 28, 2026
cff3428
What's new: link to the ASSEDEV website, links opened in a new tab
SteRiccio Oct 28, 2026
81b83d6
Merge remote-tracking branch 'origin/master' into survey-area-of-inte…
SteRiccio Oct 28, 2026
d7e112b
AOI plan: store the area of interest as survey file
SteRiccio Oct 28, 2026
a463ee2
Area of interest stored as survey file
SteRiccio Oct 28, 2026
eec823b
Spec: multiple areas of interest per survey
SteRiccio Oct 29, 2026
b309159
Plan: multiple areas of interest per survey
SteRiccio Oct 29, 2026
0fd3761
Upgrade arena-core to 2.16.0 (areas of interest provider)
SteRiccio Oct 29, 2026
72834df
Remove unused React import
SteRiccio Oct 29, 2026
de3ac47
Areas of interest: descriptors list in the survey info
SteRiccio Oct 29, 2026
d18d734
Areas of interest: survey compare, fingerprint and mobile package
SteRiccio Oct 29, 2026
f59dd9d
Areas of interest: server provider (cached by file uuid)
SteRiccio Oct 29, 2026
83adcb4
Pass areaOfInterestProvider in the record update tests
SteRiccio Oct 29, 2026
d537819
Areas of interest: editor in Survey info → Map
SteRiccio Oct 29, 2026
ea0f332
Areas of interest: expression editor
SteRiccio Oct 29, 2026
9f08815
Areas of interest: e2e spec (add / save / delete an area, area name i…
SteRiccio Oct 29, 2026
cf000f0
Areas of interest: changelog and What's new
SteRiccio Oct 29, 2026
0917da8
Integration test: NOSONAR on the sequential area file inserts
SteRiccio Oct 29, 2026
edf57c6
Merge branch 'master' into survey-area-of-interest
mergify[bot] Oct 29, 2026
b881298
Areas of interest: provider in chain results persistence and flat dat…
SteRiccio Oct 29, 2026
55858c1
Merge remote-tracking branch 'origin/survey-area-of-interest' into su…
SteRiccio Oct 29, 2026
e41e994
Area of interest dialog: name normalized while typing, name and label…
SteRiccio Oct 29, 2026
c2df5a9
Areas of interest: version 2.11.0 in the changelog and What's new
SteRiccio Oct 29, 2026
ee7d736
Merge branch 'master' into survey-area-of-interest
mergify[bot] Oct 29, 2026
1244ac6
Areas of interest: ASSEDEV Alerte Environnement credited in the chang…
SteRiccio Oct 29, 2026
8c6c196
Merge remote-tracking branch 'origin/survey-area-of-interest' into su…
SteRiccio Oct 29, 2026
48b176d
Merge branch 'master' into survey-area-of-interest
mergify[bot] Oct 29, 2026
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
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

A short, user-facing summary of each release. Internal/code-level changes are omitted; see the [git tags](https://github.com/openforis/arena/tags) for the full commit history.

- **[2.11.0](https://github.com/openforis/arena/compare/v2.10.7...v2.11.0)** — 2026-10-29: survey info: areas of interest (AOI, GeoJSON polygons) and new expression functions `geoDistanceToAOIBoundary` (distance to the boundary of an area, to check if a location is inside it taking the GPS accuracy into account) and `geoAOIContaining` (area containing a location), also offline in Arena Mobile; requested by [ASSEDEV Alerte Environnement](https://assedev.fr) (#4588)
- **[2.10.7](https://github.com/openforis/arena/compare/v2.10.6...v2.10.7)** — 2026-10-27: user groups: new "Can view the records of" setting, to give a group read-only access to the records of other groups, also in Arena Mobile; record files, exports and Arena Mobile uploads now restricted to the records accessible to the user group (#4583)
- **[2.10.6](https://github.com/openforis/arena/compare/v2.10.5...v2.10.6)** — 2026-10-26: Serbian (Latin) user interface language (#4577); Arena Mobile: faster records upload, read-only values sent by the device are no longer recalculated (#4578)
- **[2.10.5](https://github.com/openforis/arena/compare/v2.10.4...v2.10.5)** — 2026-10-25: records list: export selected records in Arena format, to import them in Arena or Arena Mobile, with the export buttons grouped in a single Export menu (#4574); survey compare: fixed Publish button disabled after applying changes (#4573); security and library updates (#4575)
Expand Down Expand Up @@ -37,7 +38,7 @@ A short, user-facing summary of each release. Internal/code-level changes are om
- **[2.5.5](https://github.com/openforis/arena/compare/v2.5.2...v2.5.5)** — 2026-06-11: Add data analyst view-not-owned-records security option (#4275); Fix/node def update (#4277)
- **[2.5.2](https://github.com/openforis/arena/compare/v2.5.0...v2.5.2)** — 2026-06-10: Chain analysis attributes: allow navigating to next and previous definition (#4273)
- **[2.5.0](https://github.com/openforis/arena/compare/v2.4.11...v2.5.0)** — 2026-06-07: Survey form node definitions labels translation Job (#4269); activity log summary: fixed layout (horizontal overflow) (#4267); Feat/ai settings loading feedback (#4263)
- **[2.4.11](https://github.com/openforis/arena/compare/v2.4.10...v2.4.11)** — 2026-05-25: Chain RScript categorical variable __CODE__ line: split it in chunks (max 200 characters) (#4253); chain summary: add description to result variables (#4252)
- **[2.4.11](https://github.com/openforis/arena/compare/v2.4.10...v2.4.11)** — 2026-05-25: Chain RScript categorical variable **CODE** line: split it in chunks (max 200 characters) (#4253); chain summary: add description to result variables (#4252)
- **[2.4.10](https://github.com/openforis/arena/compare/v2.4.9...v2.4.10)** — 2026-05-21: Survey form designer / advanced properties: added editable-if and visible-if properties (#4243)
- **[2.4.9](https://github.com/openforis/arena/compare/v2.4.8...v2.4.9)** — 2026-05-14: Show "Hidden in analytical dashboard" option in Advanced properties editor (#4241); record printable document generator: include pictures (#4236)
- **[2.4.8](https://github.com/openforis/arena/compare/v2.4.7...v2.4.8)** — 2026-05-08: Clone entities from another survey (#4235); file attribute: fixed file not visible in UI (#4234); Chain editor: fixed open RStudio not allowed (#4233)
Expand Down Expand Up @@ -82,7 +83,7 @@ A short, user-facing summary of each release. Internal/code-level changes are om
- **[2.1.60](https://github.com/openforis/arena/compare/v2.1.59...v2.1.60)** — 2025-10-06: Allow "include in records list" only to attributes (#4012); Fixed records count in summary page (merged records not taken into account) (#4011); records list / summary code attributes: show labels (#4010)
- **[2.1.59](https://github.com/openforis/arena/compare/v2.1.58...v2.1.59)** — 2025-10-03: fixed error merging records from mobile (#4008); records list sort by modified date (#4009)
- **[2.1.58](https://github.com/openforis/arena/compare/v2.1.56...v2.1.58)** — 2025-10-02: show "include in summary list" only if attribute is not inside root entity (#4007); Records list: include summary fields (#4005); Records list: show Arena Mobile 2 icon in "created with" column (#4003)
- **[2.1.56](https://github.com/openforis/arena/compare/v2.1.55...v2.1.56)** — 2025-09-23: data explorer / sort editor: allow sorting by analysis variables (#3995); Activity log: hide progress bar when there is no activity log (#3990); fixed mobile data upload handling (#3992)
- **[2.1.56](https://github.com/openforis/arena/compare/v2.1.55...v2.1.56)** — 2025-09-23: data explorer / sort editor: allow sorting by analysis variables (#3995); Activity log: hide progress bar when there is no activity log (#3990); fixed mobile data upload handling (#3992)
- **[2.1.55](https://github.com/openforis/arena/compare/v2.1.50...v2.1.55)** — 2025-09-17: Activity log delete (#3985); Arena and Collect survey upload: split file in chunks (#3984)
- **[2.1.50](https://github.com/openforis/arena/compare/v2.1.49...v2.1.50)** — 2025-09-05: fixed code attribute default render to dropdown (#3969); Category item code: allow uppercase (#3973)
- **[2.1.49](https://github.com/openforis/arena/compare/v2.1.48...v2.1.49)** — 2025-09-03: fixed form edit scroll to top on node def change (#3970)
Expand Down Expand Up @@ -141,7 +142,7 @@ A short, user-facing summary of each release. Internal/code-level changes are om
- **[1.4.57](https://github.com/openforis/arena/compare/v1.4.54...v1.4.57)** — 2024-10-29: Fix/survey info authorization (#3625); fixed chain: order of selected dimensions (#3621)
- **[1.4.54](https://github.com/openforis/arena/compare/v1.4.53...v1.4.54)** — 2024-10-24: Unique validation: added info text (#3615); Survey clone: divide surveys and templates; sort by name (#3613); hierarchical code attribute: show items in dropdown list (#3611)
- **[1.4.53](https://github.com/openforis/arena/compare/v1.4.52...v1.4.53)** — 2024-10-16: survey create: improve error message if max number of surveys is reached (#3601)
- **[1.4.52](https://github.com/openforis/arena/compare/v1.4.50...v1.4.52)** — 2024-10-07: form designer - full tree view (#3514); Fixed date parsing in data entry (#3590); Files storage: allow specifying max files (#3585)
- **[1.4.52](https://github.com/openforis/arena/compare/v1.4.50...v1.4.52)** — 2024-10-07: form designer - full tree view (#3514); Fixed date parsing in data entry (#3590); Files storage: allow specifying max files (#3585)
- **[1.4.50](https://github.com/openforis/arena/compare/v1.4.49...v1.4.50)** — 2024-09-20: fixed survey hierarchy items not shown in order (#3572); Do not allow convert attribute into entity (#3569); fixed boolean attribute layout issue (#3578)
- **[1.4.49](https://github.com/openforis/arena/compare/v1.4.48...v1.4.49)** — 2024-09-12: Auto incremental key: use Math.max to calculate next value; do not keep default values when converting node def; (#3568); Auto incremental key (#3567); escape special characters in generated file names (#3565)
- **[1.4.48](https://github.com/openforis/arena/compare/v1.4.47...v1.4.48)** — 2024-09-09: fixed dropzone invalid file error message (#3559); New survey (clone from existing one): fixed surveys dropdown sorting (by name) (#3547); Surveys list: fixed "only own" filtering (#3552)
Expand All @@ -162,7 +163,7 @@ A short, user-facing summary of each release. Internal/code-level changes are om
- **[1.4.30](https://github.com/openforis/arena/compare/v1.4.29...v1.4.30)** — 2024-04-19: ReCaptha enabled env var: do not stringify it to use it in client (Heroku passes it as a string already) (#3380); Survey ref data index: exclude big taxonomies in UI (#3381); make ReCaptcha optional (#3371)
- **[1.4.29](https://github.com/openforis/arena/compare/v1.4.27...v1.4.29)** — 2024-04-16: data query storage (#3363); fixed Data Editor cannot see list of records (#3365)
- **[1.4.27](https://github.com/openforis/arena/compare/v1.4.26...v1.4.27)** — 2024-04-12: query attributes sorting (#3353); Layout adjustments: access request page (#3355); guest pages: layout adjustments (#3354)
- **[1.4.26](https://github.com/openforis/arena/compare/v1.4.25...v1.4.26)** — 2024-03-28: chain two-phase sampling common attribute (#3340); fixed runtime error opening record (multiple attribute null values) (#3349); RStudio scripts expiring after 48H (#3346)
- **[1.4.26](https://github.com/openforis/arena/compare/v1.4.25...v1.4.26)** — 2024-03-28: chain two-phase sampling common attribute (#3340); fixed runtime error opening record (multiple attribute null values) (#3349); RStudio scripts expiring after 48H (#3346)
- **[1.4.25](https://github.com/openforis/arena/compare/v1.4.24...v1.4.25)** — 2024-03-18: fixing category validation (#3328)
- **[1.4.24](https://github.com/openforis/arena/compare/v1.4.23...v1.4.24)** — 2024-03-18: Fixed category validation (#3327)
- **[1.4.23](https://github.com/openforis/arena/compare/v1.4.22...v1.4.23)** — 2024-03-17: Added category and taxonomy items counts in table (#3317); Expression editor: hide this var (#3319)
Expand Down Expand Up @@ -220,7 +221,7 @@ A short, user-facing summary of each release. Internal/code-level changes are om
- **[1.0.10](https://github.com/openforis/arena/compare/v1.0.9...v1.0.10)** — 2021-09-15: promote/demote all records: update only records in selected cycle (#1790); changed exported file name (#1789); fixed blank screen on record delete (#1788)
- **[1.0.9](https://github.com/openforis/arena/compare/v1.0.8...v1.0.9)** — 2021-09-14: validate only modified node defs survey publish (#1775); exclude analysis variables from validation report (#1774); numeric attribute: do not validate max decimal digits (#1773)
- **[1.0.8](https://github.com/openforis/arena/compare/v1.0.7...v1.0.8)** — 2021-08-27: tentative to fix first time survey showing on cloned/published survey (#1716); Enhancement/delete empty taxonomy (#1715); Enhancement/delete empty category (#1711)
- **[1.0.7](https://github.com/openforis/arena/compare/v1.0.6...v1.0.7)** — 2021-08-23: Expanded and ignore (#1682); Keep focus input cell (#1675); Bugfix/sidebar items hidden with low res (#1657)
- **[1.0.7](https://github.com/openforis/arena/compare/v1.0.6...v1.0.7)** — 2021-08-23: Expanded and ignore (#1682); Keep focus input cell (#1675); Bugfix/sidebar items hidden with low res (#1657)
- **[1.0.6](https://github.com/openforis/arena/compare/v1.0.5...v1.0.6)** — 2021-08-05: Rstudio local modal (#1652); Enhancement/survey manager role (#1646); show date input field when defining constant value of type date in basic expression editor (#1645)
- **[1.0.5](https://github.com/openforis/arena/compare/v1.0.4...v1.0.5)** — 2021-07-26: assign unique survey name when importing survey from Collect (#1622); Show message based on count (#1617); format key date attributes in records list (#1614)
- **[1.0.4](https://github.com/openforis/arena/compare/v1.0.3...v1.0.4)** — 2021-07-24: Enhancement / node def description (#1608); do not show first time help for surveys imported from Collect; fixed error importing survey from Collect
Expand Down
2 changes: 2 additions & 0 deletions core/expressionParser/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ export const functionNames = {
dateTimeDiff: 'dateTimeDiff',
distance: 'distance',
first: 'first',
geoAOIContaining: 'geoAOIContaining',
geoCoordinateAtDistance: 'geoCoordinateAtDistance',
geoDistance: 'geoDistance',
geoDistanceToAOIBoundary: 'geoDistanceToAOIBoundary',
geoPolygon: 'geoPolygon',
includes: 'includes',
index: 'index',
Expand Down
89 changes: 89 additions & 0 deletions core/geo/areaOfInterestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { AreaOfInterest } from '@openforis/arena-core'
import type { Position } from 'geojson'

type GeoJsonObject = {
type?: string
coordinates?: unknown
geometry?: unknown
features?: unknown
}

const isPosition = (value: unknown): value is Position => {
if (!Array.isArray(value) || value.length < 2) return false
const [x, y] = value
return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) <= 180 && Math.abs(y) <= 90
}

// a linear ring has at least 4 positions (the last one equal to the first one)
const isLinearRing = (value: unknown): value is Position[] =>
Array.isArray(value) && value.length >= 4 && value.every(isPosition)

const isPolygonCoordinates = (value: unknown): value is Position[][] =>
Array.isArray(value) && value.length > 0 && value.every(isLinearRing)

// returns the polygons (as list of rings) of a Polygon or MultiPolygon geometry, or null if not valid
const extractPolygonsFromGeometry = (geometry: unknown): Position[][][] | null => {
const { type, coordinates } = (geometry ?? {}) as GeoJsonObject
if (type === 'Polygon') {
return isPolygonCoordinates(coordinates) ? [coordinates] : null
}
if (type === 'MultiPolygon') {
return Array.isArray(coordinates) && coordinates.length > 0 && coordinates.every(isPolygonCoordinates)
? coordinates
: null
}
return null
}

const extractPolygons = (geoJson: unknown): Position[][][] | null => {
const { type, geometry, features } = (geoJson ?? {}) as GeoJsonObject
if (type === 'Feature') return extractPolygonsFromGeometry(geometry)
if (type === 'FeatureCollection') {
if (!Array.isArray(features)) return null
// features with other geometry types (e.g. points or lines) are ignored
const polygons = features.flatMap((feature) => extractPolygons(feature) ?? [])
return polygons.length > 0 ? polygons : null
}
return extractPolygonsFromGeometry(geoJson)
}

/**
* Extracts the area of interest from a GeoJSON object (Polygon or MultiPolygon geometry, Feature or FeatureCollection).
*
* @param {unknown} geoJson - The GeoJSON object.
* @returns {AreaOfInterest|null} - A Polygon (single polygon) or MultiPolygon geometry, or null if no valid polygon is found.
*/
export const parseAreaOfInterest = (geoJson: unknown): AreaOfInterest | null => {
const polygons = extractPolygons(geoJson)
if (!polygons) return null
return polygons.length === 1
? { type: 'Polygon', coordinates: polygons[0] }
: { type: 'MultiPolygon', coordinates: polygons }
}

/**
* Extracts the area of interest from the text of a GeoJSON file.
*
* @param {string} text - The GeoJSON text.
* @returns {AreaOfInterest|null} - The area of interest or null if the text is not valid JSON or has no valid polygon.
*/
export const parseAreaOfInterestJson = (text: string): AreaOfInterest | null => {
try {
return parseAreaOfInterest(JSON.parse(text))
} catch {
// not a valid JSON text
return null
}
}

/**
* Returns the number of polygons and vertices of an area of interest.
*
* @param {AreaOfInterest} areaOfInterest - The area of interest.
* @returns {{polygons: number, vertices: number}} - Number of polygons and vertices.
*/
export const getAreaOfInterestSummary = (areaOfInterest: AreaOfInterest): { polygons: number; vertices: number } => {
const polygons = areaOfInterest.type === 'Polygon' ? [areaOfInterest.coordinates] : areaOfInterest.coordinates
const vertices = polygons.reduce((acc, rings) => acc + rings.reduce((accRings, ring) => accRings + ring.length, 0), 0)
return { polygons: polygons.length, vertices }
}
6 changes: 6 additions & 0 deletions core/i18n/resources/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,7 @@ $t(common.cantUndoWarning)`,
noItemsAdded: 'No items added',
},
expression: {
areaOfInterestNotFound: 'Area of interest "{{name}}" not found: define it in Survey info → Map',
functionHasTooFewArguments: 'Function {{fnName}} requires at least {{minArity}} arguments (got {{numArgs}})',
functionHasTooManyArguments: 'Function {{fnName}} only accepts at most {{maxArity}} arguments (got {{numArgs}})',
identifierNotFound: 'Attribute or entity "{{name}}" not found',
Expand Down Expand Up @@ -1106,6 +1107,11 @@ A '${locationItemExtraDefName}' extra property will be added to the items.`,
distanceAttribute: 'Distance attribute',
bearingAttribute: 'Bearing attribute',
},
areaOfInterestEditor: {
areaOfInterest: 'Area of interest',
areasOfInterestNotDefined: 'No areas of interest defined yet: define them in Survey info → Map',
},
coordinateAttribute: 'Coordinate attribute',
coordinateAttributeWithPosition: 'Coordinate attribute {{position}}',

dateTimeDiffEditor: {
Expand Down
13 changes: 13 additions & 0 deletions core/i18n/resources/en/homeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ When checked, those values are kept in the record, even if the attributes are no
map: 'Map',
viewInfo: 'View info',

areasOfInterest: {
title: 'Areas of interest',
info: 'Polygons (GeoJSON, WGS84 coordinates) used by the expression functions geoDistanceToAOIBoundary (distance to the boundary of an area) and geoAOIContaining (area containing a location), also offline in Arena Mobile. The name of an area is used in the expressions.',
name: 'Name',
polygons: 'Polygons',
vertices: 'Vertices',
invalidFile: 'The file does not contain a valid polygon in GeoJSON format with WGS84 coordinates',
confirmDelete: 'Are you sure you want to delete this area of interest?',
editor: {
title: 'Area of interest',
},
},

preloadedMapLayers: {
enabledMessage: 'Preloaded map layers enabled',
title: 'Preloaded map layers',
Expand Down
4 changes: 4 additions & 0 deletions core/i18n/resources/en/nodeDefEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export default {
geoCoordinateAtDistance:
'Returns the coordinates at a specified distance and bearing from the specified coordinates',
geoDistance: '$t(nodeDefEdit:functionDescriptions.distance)',
geoAOIContaining:
'Returns the name of the first area of interest of the survey (in the order defined in Survey info → Map) containing the specified coordinate, or nothing if no area contains it',
geoDistanceToAOIBoundary:
'Returns the distance (in meters) between a coordinate and the boundary of the specified area of interest of the survey: negative inside the area, positive outside',
geoPolygon: 'Generates a polygon in GeoJSON from a list of coordinates',
includes: 'Returns true if the specified multiple attribute includes the specified value.',
index: 'Returns the index of the specified node among its siblings',
Expand Down
1 change: 1 addition & 0 deletions core/i18n/resources/en/surveyCompareView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Changes are saved as draft: they will reach the records only when the survey is
brandingSurveyLogo2: 'Survey logo 2',
brandingSurveyLogo3: 'Survey logo 3',
brandingLandingBackground: 'Landing page background',
areaOfInterest: 'Area of interest',
},
cycle: 'cycle {{cycle}}',
property: 'Property',
Expand Down
7 changes: 7 additions & 0 deletions core/i18n/resources/en/validationErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ export default {
cycleDateEndInvalid: 'Cycle end date is invalid',
cycleDateEndMandatoryExceptForLastCycle: 'Cycle end date is mandatory for all but the last cycle',
fieldManualLinksInvalid: 'Field manual link is invalid',
areaOfInterestInvalid: 'Area of interest must be a polygon in GeoJSON format with WGS84 coordinates',
},

surveyAreaOfInterest: {
fileRequired: 'File is required',
nameDuplicate: 'Another area of interest with the same name already exists',
nameRequired: 'Name is required',
},

surveyPreloadedMapLayer: {
Expand Down
Loading
Loading