Skip to content

Commit

Permalink
SW-6191 Change monitoring plot size to 30 meters (#2549)
Browse files Browse the repository at this point in the history
Update the monitoring plot size so that any newly-created plots will be 30 meters
wide and aligned on a 30-meter-interval grid from the planting site's grid origin.

Existing 25-meter plots are not affected; this only changes the size that's used
for new plots going forward.
  • Loading branch information
sgrimm authored Nov 1, 2024
1 parent 12f1a39 commit 8d27843
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const val HECTARES_SCALE = 1
val MAX_SITE_ENVELOPE_AREA_HA = BigDecimal(20000)

/** Monitoring plot width and height in meters. */
const val MONITORING_PLOT_SIZE: Double = 25.0
const val MONITORING_PLOT_SIZE: Double = 30.0

/** Monitoring plot width and height in meters. */
const val MONITORING_PLOT_SIZE_INT = MONITORING_PLOT_SIZE.toInt()
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,16 @@ data class PlantingZoneModel<PZID : PlantingZoneId?, PSZID : PlantingSubzoneId?>
}

/**
* Returns the monitoring plot that contains the center point of a shape, or null if the shape
* isn't in any plot.
* Returns the monitoring plot that is of the correct size and contains the center point of a
* shape, or null if the shape isn't in any plot.
*/
fun findMonitoringPlot(geometry: Geometry): MonitoringPlotModel? {
val centroid = geometry.centroid

return plantingSubzones.firstNotNullOfOrNull { subzone ->
subzone.monitoringPlots.firstOrNull { plot -> plot.boundary.contains(centroid) }
subzone.monitoringPlots.firstOrNull { plot ->
plot.boundary.contains(centroid) && plot.sizeMeters == MONITORING_PLOT_SIZE_INT
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class TrackingSearchTest : DatabaseTest(), RunsAsUser {
plantingCompletedTime = Instant.ofEpochSecond(1),
name = "4")
val monitoringPlotId7 = insertMonitoringPlot(boundary = monitoringPlotGeometry7)
val monitoringPlotId8 = insertMonitoringPlot(boundary = monitoringPlotGeometry8)
val monitoringPlotId8 =
insertMonitoringPlot(boundary = monitoringPlotGeometry8, sizeMeters = 25)

val speciesId1 = insertSpecies()
val speciesId2 = insertSpecies()
Expand Down Expand Up @@ -314,7 +315,7 @@ class TrackingSearchTest : DatabaseTest(), RunsAsUser {
"northeastLongitude" to "7",
"northwestLatitude" to "8",
"northwestLongitude" to "5",
"sizeMeters" to "25",
"sizeMeters" to "30",
"southeastLatitude" to "6",
"southeastLongitude" to "7",
"southwestLatitude" to "6",
Expand All @@ -326,7 +327,7 @@ class TrackingSearchTest : DatabaseTest(), RunsAsUser {
"northeastLongitude" to "8",
"northwestLatitude" to "9",
"northwestLongitude" to "6",
"sizeMeters" to "25",
"sizeMeters" to "30",
"southeastLatitude" to "7",
"southeastLongitude" to "8",
"southwestLatitude" to "7",
Expand Down Expand Up @@ -362,7 +363,7 @@ class TrackingSearchTest : DatabaseTest(), RunsAsUser {
"northeastLongitude" to "9",
"northwestLatitude" to "10",
"northwestLongitude" to "7",
"sizeMeters" to "25",
"sizeMeters" to "30",
"southeastLatitude" to "8",
"southeastLongitude" to "9",
"southwestLatitude" to "8",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class ObservationStoreTest : DatabaseTest(), RunsAsUser {
plantingSubzoneId = plantingSubzoneId1,
plantingSubzoneName = "Z1-S1",
plotName = "Z1-S1-1",
sizeMeters = 25,
sizeMeters = 30,
),
AssignedPlotDetails(
model =
Expand All @@ -225,7 +225,7 @@ class ObservationStoreTest : DatabaseTest(), RunsAsUser {
plantingSubzoneId = plantingSubzoneId1,
plantingSubzoneName = "Z1-S1",
plotName = "Z1-S1-2",
sizeMeters = 25,
sizeMeters = 30,
),
AssignedPlotDetails(
model =
Expand All @@ -247,7 +247,7 @@ class ObservationStoreTest : DatabaseTest(), RunsAsUser {
plantingSubzoneId = plantingSubzoneId2,
plantingSubzoneName = "Z1-S2",
plotName = "Z1-S2-1",
sizeMeters = 25,
sizeMeters = 30,
))

val actual = store.fetchObservationPlotDetails(observationId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,16 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() {
@Test
fun `can use temporary plot from previous observation as permanent plot`() {
val gridOrigin = point(0)
val siteBoundary = Turtle(gridOrigin).makeMultiPolygon { rectangle(51, 26) }
val siteBoundary =
Turtle(gridOrigin).makeMultiPolygon {
rectangle(MONITORING_PLOT_SIZE * 2 + 1, MONITORING_PLOT_SIZE + 1)
}

// Temporary plot is in the east half of the site.
val existingPlotBoundary =
Turtle(gridOrigin).makePolygon {
east(25)
square(25)
east(MONITORING_PLOT_SIZE)
square(MONITORING_PLOT_SIZE)
}

val plantingSiteId = insertPlantingSite(boundary = siteBoundary, gridOrigin = gridOrigin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ internal class PlantingSiteStoreReadTest : PlantingSiteStoreTest() {
isAvailable = true,
name = "1",
fullName = "Z1-1-1",
sizeMeters = 25)),
sizeMeters = 30)),
)))))

val allExpected =
Expand Down Expand Up @@ -459,7 +459,7 @@ internal class PlantingSiteStoreReadTest : PlantingSiteStoreTest() {
isAvailable = true,
name = "1",
fullName = "Z1-1-1",
sizeMeters = 25)),
sizeMeters = 30)),
)))))

val actual = store.fetchSiteById(plantingSiteId, PlantingSiteDepth.Plot)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,24 +158,28 @@ class PlantingZoneModelTest {

@Test
fun `does not choose plots that lie partially outside subzone`() {
// Zone is 76 meters by 26 meters, split into two 38x26 subzones such that there are three
// plot locations but the middle one sits on the subzone boundary. Only subzone 1 is planted.
val subzone1Boundary = Turtle(siteOrigin).makeMultiPolygon { rectangle(38, 26) }
// Zone is three plots wide by one plot high, split horizontally into two equal-sized subzones
// such that there are three plot locations but the middle one sits on the subzone boundary.
// Only subzone 1 is planted.
val subzoneWidth = MONITORING_PLOT_SIZE * 1.5
val subzoneHeight = MONITORING_PLOT_SIZE + 1
val subzone1Boundary =
Turtle(siteOrigin).makeMultiPolygon { rectangle(subzoneWidth, subzoneHeight) }
val subzone2Boundary =
Turtle(siteOrigin).makeMultiPolygon {
east(38)
rectangle(38, 26)
east(subzoneWidth)
rectangle(subzoneWidth, subzoneHeight)
}
val subzone1PlotBoundary = Turtle(siteOrigin).makePolygon { square(25) }
val subzone1PlotBoundary = Turtle(siteOrigin).makePolygon { square(MONITORING_PLOT_SIZE) }
val bothSubzonesPlotBoundary =
Turtle(siteOrigin).makePolygon {
east(25)
square(25)
east(MONITORING_PLOT_SIZE)
square(MONITORING_PLOT_SIZE)
}
val subzone2PlotBoundary =
Turtle(siteOrigin).makePolygon {
east(50)
square(25)
east(MONITORING_PLOT_SIZE * 2)
square(MONITORING_PLOT_SIZE)
}

val model =
Expand Down Expand Up @@ -567,9 +571,9 @@ class PlantingZoneModelTest {

val targetArea =
Turtle(siteOrigin).makePolygon {
east(Random.nextInt(edgeMeters - 51))
north(Random.nextInt(edgeMeters - 51))
square(51)
east(Random.nextInt(edgeMeters - 61))
north(Random.nextInt(edgeMeters - 61))
square(61)
}

val siteBoundary = geometryFactory.createMultiPolygon((triangles + targetArea).toTypedArray())
Expand All @@ -578,7 +582,7 @@ class PlantingZoneModelTest {
boundary = siteBoundary,
subzones = listOf(plantingSubzoneModel(boundary = siteBoundary, plots = emptyList())))

val square = zone.findUnusedSquare(siteOrigin, 25)
val square = zone.findUnusedSquare(siteOrigin, 30)
assertNotNull(square, "Unused square")

if (square!!.intersection(targetArea).area < square.area * 0.99999) {
Expand All @@ -591,9 +595,9 @@ class PlantingZoneModelTest {
inner class FindUnusedSquares {
@Test
fun `excludes permanent monitoring plots`() {
// Boundary is a 51x26m square, and there is an existing plot in the southwestern 25x25m.
val siteBoundary = Turtle(siteOrigin).makeMultiPolygon { rectangle(51, 26) }
val existingPlotPolygon = Turtle(siteOrigin).makePolygon { square(25) }
// Boundary is a 61x31m square, and there is an existing plot in the southwestern 30x30m.
val siteBoundary = Turtle(siteOrigin).makeMultiPolygon { rectangle(61, 31) }
val existingPlotPolygon = Turtle(siteOrigin).makePolygon { square(30) }

val zone =
plantingZoneModel(
Expand All @@ -609,15 +613,15 @@ class PlantingZoneModelTest {

val expected =
Turtle(siteOrigin).makePolygon {
east(25)
square(25)
east(30)
square(30)
}

repeat(20) {
val actual =
zone.findUnusedSquares(
gridOrigin = siteOrigin,
sizeMeters = 25,
sizeMeters = 30,
count = 1,
excludeAllPermanentPlots = true)

Expand Down Expand Up @@ -658,13 +662,13 @@ class PlantingZoneModelTest {

return Turtle(siteOrigin).makePolygon {
// Subzone corner
east(subzoneId * 50)
north(25 * (plotNumber / 2))
east(subzoneId * MONITORING_PLOT_SIZE * 2)
north(MONITORING_PLOT_SIZE * (plotNumber / 2))
if (plotNumber.rem(2) == 1) {
east(25)
east(MONITORING_PLOT_SIZE)
}

square(25)
square(MONITORING_PLOT_SIZE)
}
}

Expand All @@ -682,7 +686,7 @@ class PlantingZoneModelTest {
name = "name",
permanentCluster = permanentCluster,
permanentClusterSubplot = if (permanentCluster != null) 1 else null,
sizeMeters = 25,
sizeMeters = MONITORING_PLOT_SIZE_INT,
)
}

Expand All @@ -703,27 +707,27 @@ class PlantingZoneModelTest {
*/
private fun plantingSubzoneBoundary(id: Int, numPlots: Int): MultiPolygon {
return Turtle(siteOrigin).makeMultiPolygon {
east(id * 50)
east(id * MONITORING_PLOT_SIZE * 2)

val southwest = currentPosition

// Figure out the northwest corner's location.
north(((numPlots + 1) / 2) * 25)
north(((numPlots + 1) / 2) * MONITORING_PLOT_SIZE)
val northwest = currentPosition

moveTo(southwest)

startDrawing()
east(50)
north((numPlots / 2) * 25)
east(MONITORING_PLOT_SIZE * 2)
north((numPlots / 2) * MONITORING_PLOT_SIZE)

if (numPlots.and(1) == 0) {
// Even number of plots; the boundary is a plain rectangle.
moveTo(northwest)
} else {
// Odd number of plots; space for the last plot is on the west half of the northern edge.
west(25)
north(25)
west(MONITORING_PLOT_SIZE)
north(MONITORING_PLOT_SIZE)
moveTo(northwest)
}
}
Expand Down

0 comments on commit 8d27843

Please sign in to comment.