Skip to content

Commit 1dfdb72

Browse files
committed
Add point_on_feature examples with visualizations
1 parent a343ee7 commit 1dfdb72

16 files changed

+1115
-0
lines changed
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Point on Feature Examples
2+
3+
This directory contains examples demonstrating the `pointOnFeature` function in the turf_dart library.
4+
5+
## Function Overview
6+
7+
The `pointOnFeature` function returns a point that is guaranteed to be on or inside a feature. This is useful for placing labels, icons, or other markers on geographic features.
8+
9+
The function behavior varies by geometry type:
10+
- **Point**: Returns the original point unchanged
11+
- **LineString**: Returns the midpoint of the first segment
12+
- **Polygon**: Returns a point inside the polygon (preferably the centroid)
13+
- **MultiPolygon**: Uses the first polygon to compute a point
14+
- **FeatureCollection**: Returns a point on the largest feature
15+
16+
## Directory Structure
17+
18+
- **`/in`**: Input GeoJSON files with different geometry types
19+
- **`/out`**: Output files showing the points generated by `pointOnFeature`
20+
- **`visualization.geojson`**: Combined visualization of all inputs and their resulting points
21+
22+
## Example Files
23+
24+
1. **Point Example**: Shows that `pointOnFeature` returns the original point
25+
2. **LineString Example**: Shows how `pointOnFeature` finds the midpoint of the first line segment
26+
3. **Polygon Examples**: Show how `pointOnFeature` returns a point inside the polygon
27+
4. **MultiPolygon Example**: Shows how `pointOnFeature` uses the first polygon
28+
5. **FeatureCollection Example**: Shows how `pointOnFeature` finds a point on the largest feature
29+
30+
## Visualization
31+
32+
The `visualization.geojson` file combines all examples into one visualization. When viewed in a GeoJSON viewer, it shows:
33+
- Original geometries in blue
34+
- Points generated by `pointOnFeature` in red with different markers:
35+
- Stars for points
36+
- Circles for linestrings
37+
- Triangles for polygons
38+
- Squares for multipolygons
39+
- Circle-stroked for feature collections
40+
41+
Each point includes a description explaining how it was generated.
42+
43+
## Running the Examples
44+
45+
To regenerate the examples and visualization:
46+
47+
1. Run the output generator:
48+
```
49+
dart test/examples/point_on_feature/generate_outputs.dart
50+
```
51+
52+
2. Run the visualization generator:
53+
```
54+
dart test/examples/point_on_feature/generate_visualization.dart
55+
```
56+
57+
## Use Cases
58+
59+
The `pointOnFeature` function is commonly used for:
60+
- Placing labels on geographic features
61+
- Positioning icons or markers on features
62+
- Finding representative points for complex geometries
63+
- Generating points for clustering or other spatial operations
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'package:turf/turf.dart';
4+
5+
void main() async {
6+
// Process each input file
7+
await processFile('polygon_feature.geojson');
8+
await processFile('polygon.geojson');
9+
await processFile('point.geojson');
10+
await processFile('linestring.geojson');
11+
await processFile('multipolygon.geojson');
12+
await processFile('featurecollection.geojson');
13+
14+
print('All files processed successfully!');
15+
}
16+
17+
Future<void> processFile(String filename) async {
18+
try {
19+
final inputPath = 'test/examples/point_on_feature/in/$filename';
20+
final outputPath = 'test/examples/point_on_feature/out/$filename';
21+
22+
print('Processing $inputPath');
23+
24+
// Read the input file
25+
final file = File(inputPath);
26+
final jsonString = await file.readAsString();
27+
final geojson = jsonDecode(jsonString);
28+
29+
// Parse the GeoJSON and create appropriate object based on type
30+
dynamic featureInput;
31+
32+
if (geojson['type'] == 'Feature') {
33+
featureInput = Feature.fromJson(geojson);
34+
} else if (geojson['type'] == 'FeatureCollection') {
35+
featureInput = FeatureCollection.fromJson(geojson);
36+
} else {
37+
// For raw geometry objects, create a Feature with the geometry
38+
final geometry = parseGeometry(geojson);
39+
if (geometry != null) {
40+
featureInput = Feature(geometry: geometry);
41+
} else {
42+
print(' Unsupported geometry type: ${geojson['type']}');
43+
return;
44+
}
45+
}
46+
47+
// Apply point_on_feature function
48+
final pointResult = pointOnFeature(featureInput);
49+
50+
// Generate output - wrap in a FeatureCollection for better compatibility
51+
Map<String, dynamic> outputJson;
52+
53+
if (pointResult != null) {
54+
final features = <Feature>[];
55+
56+
// Create a new feature based on the input geometry
57+
GeometryObject? inputGeometry;
58+
if (featureInput is Feature) {
59+
inputGeometry = featureInput.geometry;
60+
} else if (featureInput is FeatureCollection && featureInput.features.isNotEmpty) {
61+
inputGeometry = featureInput.features[0].geometry;
62+
} else {
63+
inputGeometry = parseGeometry(geojson);
64+
}
65+
66+
if (inputGeometry != null) {
67+
// Create a new feature with the input geometry
68+
final styledInputFeature = Feature(
69+
geometry: inputGeometry,
70+
properties: {
71+
'name': 'Input Geometry',
72+
'description': 'Original geometry from $filename'
73+
}
74+
);
75+
76+
// Add styling based on geometry type
77+
if (inputGeometry is Polygon || inputGeometry is MultiPolygon) {
78+
styledInputFeature.properties!['stroke'] = '#0000FF';
79+
styledInputFeature.properties!['stroke-width'] = 2;
80+
styledInputFeature.properties!['fill'] = '#0000FF';
81+
styledInputFeature.properties!['fill-opacity'] = 0.2;
82+
} else if (inputGeometry is LineString || inputGeometry is MultiLineString) {
83+
styledInputFeature.properties!['stroke'] = '#0000FF';
84+
styledInputFeature.properties!['stroke-width'] = 2;
85+
} else if (inputGeometry is Point) {
86+
styledInputFeature.properties!['marker-color'] = '#0000FF';
87+
}
88+
89+
features.add(styledInputFeature);
90+
}
91+
92+
// Create a new feature for the point result to avoid modifying unmodifiable maps
93+
final styledPointResult = Feature(
94+
geometry: pointResult.geometry,
95+
properties: {
96+
'marker-color': '#FF0000',
97+
'marker-size': 'medium',
98+
'marker-symbol': 'star',
99+
'name': 'Point on Feature Result',
100+
'description': 'Point generated by pointOnFeature function'
101+
}
102+
);
103+
104+
features.add(styledPointResult);
105+
106+
outputJson = FeatureCollection(features: features).toJson();
107+
print(' Found point at coordinates: ${pointResult.geometry?.coordinates}');
108+
} else {
109+
// Create an empty FeatureCollection with error info in properties
110+
outputJson = {
111+
'type': 'FeatureCollection',
112+
'features': [
113+
{
114+
'type': 'Feature',
115+
'properties': {
116+
'error': 'Could not generate point for this input',
117+
'name': 'Error',
118+
'description': 'pointOnFeature function could not generate a point'
119+
},
120+
'geometry': null
121+
}
122+
]
123+
};
124+
print(' Could not generate point for this input');
125+
}
126+
127+
// Write to output file with pretty formatting
128+
final outputFile = File(outputPath);
129+
await outputFile.writeAsString(JsonEncoder.withIndent(' ').convert(outputJson));
130+
print(' Saved result to $outputPath');
131+
} catch (e) {
132+
print('Error processing $filename: $e');
133+
}
134+
}
135+
136+
GeometryObject? parseGeometry(Map<String, dynamic> json) {
137+
final type = json['type'];
138+
139+
switch (type) {
140+
case 'Point':
141+
return Point.fromJson(json);
142+
case 'LineString':
143+
return LineString.fromJson(json);
144+
case 'Polygon':
145+
return Polygon.fromJson(json);
146+
case 'MultiPoint':
147+
return MultiPoint.fromJson(json);
148+
case 'MultiLineString':
149+
return MultiLineString.fromJson(json);
150+
case 'MultiPolygon':
151+
return MultiPolygon.fromJson(json);
152+
case 'GeometryCollection':
153+
return GeometryCollection.fromJson(json);
154+
default:
155+
return null;
156+
}
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'package:turf/turf.dart';
4+
5+
void main() async {
6+
// Create a single visualization file showing all inputs and their corresponding points
7+
await generateVisualization();
8+
9+
print('Visualization file generated successfully!');
10+
}
11+
12+
Future<void> generateVisualization() async {
13+
try {
14+
final files = [
15+
'polygon_feature.geojson',
16+
'polygon.geojson',
17+
'linestring.geojson',
18+
'multipolygon.geojson',
19+
'point.geojson',
20+
'featurecollection.geojson'
21+
];
22+
23+
// List to store all features
24+
final allFeatures = <Feature>[];
25+
26+
// Process each output file only (since they now contain both input and result)
27+
for (final filename in files) {
28+
final outputPath = 'test/examples/point_on_feature/out/$filename';
29+
30+
print('Processing $filename for visualization');
31+
32+
// Read the output file
33+
final outputFile = File(outputPath);
34+
35+
if (!outputFile.existsSync()) {
36+
print(' Missing output file for $filename, skipping');
37+
continue;
38+
}
39+
40+
final outputJson = jsonDecode(await outputFile.readAsString());
41+
42+
// The output files are already FeatureCollections with styled features
43+
if (outputJson['type'] == 'FeatureCollection' && outputJson['features'] is List) {
44+
final outFeatures = outputJson['features'] as List;
45+
46+
// Add custom markers based on the geometry type for the result point
47+
for (final feature in outFeatures) {
48+
if (feature['properties'] != null &&
49+
feature['properties']['name'] == 'Point on Feature Result') {
50+
51+
// Update description based on geometry type
52+
if (filename == 'point.geojson') {
53+
feature['properties']['description'] = 'Point on a Point: Returns the original point unchanged';
54+
feature['properties']['marker-symbol'] = 'star';
55+
} else if (filename == 'linestring.geojson') {
56+
feature['properties']['description'] = 'Point on a LineString: Returns the midpoint of the first segment';
57+
feature['properties']['marker-symbol'] = 'circle';
58+
} else if (filename.contains('polygon') && !filename.contains('multi')) {
59+
feature['properties']['description'] = 'Point on a Polygon: Returns a point inside the polygon (prefers centroid)';
60+
feature['properties']['marker-symbol'] = 'triangle';
61+
} else if (filename == 'multipolygon.geojson') {
62+
feature['properties']['description'] = 'Point on a MultiPolygon: Returns a point from the first polygon';
63+
feature['properties']['marker-symbol'] = 'square';
64+
} else if (filename == 'featurecollection.geojson') {
65+
feature['properties']['description'] = 'Point on a FeatureCollection: Returns a point on the largest feature';
66+
feature['properties']['marker-symbol'] = 'circle-stroked';
67+
}
68+
69+
feature['properties']['name'] = 'Result: $filename';
70+
}
71+
72+
// Add the feature to our collection
73+
try {
74+
final parsedFeature = Feature.fromJson(feature);
75+
allFeatures.add(parsedFeature);
76+
} catch (e) {
77+
print(' Error parsing feature: $e');
78+
}
79+
}
80+
}
81+
}
82+
83+
// Create the feature collection
84+
final featureCollection = FeatureCollection(features: allFeatures);
85+
86+
// Save the visualization file with pretty formatting
87+
final visualizationFile = File('test/examples/point_on_feature/visualization.geojson');
88+
await visualizationFile.writeAsString(JsonEncoder.withIndent(' ').convert(featureCollection.toJson()));
89+
90+
print('Saved visualization to ${visualizationFile.path}');
91+
} catch (e) {
92+
print('Error generating visualization: $e');
93+
}
94+
}
95+
96+
// Helper function to set style properties for features
97+
void setFeatureStyle(Feature feature, String color, int width, double opacity) {
98+
feature.properties = feature.properties ?? {};
99+
100+
// Different styling based on geometry type
101+
if (feature.geometry is Polygon || feature.geometry is MultiPolygon) {
102+
feature.properties!['stroke'] = color;
103+
feature.properties!['stroke-width'] = width;
104+
feature.properties!['stroke-opacity'] = 1;
105+
feature.properties!['fill'] = color;
106+
feature.properties!['fill-opacity'] = opacity;
107+
} else if (feature.geometry is LineString || feature.geometry is MultiLineString) {
108+
feature.properties!['stroke'] = color;
109+
feature.properties!['stroke-width'] = width;
110+
feature.properties!['stroke-opacity'] = 1;
111+
} else if (feature.geometry is Point || feature.geometry is MultiPoint) {
112+
feature.properties!['marker-color'] = color;
113+
feature.properties!['marker-size'] = 'small';
114+
}
115+
}
116+
117+
// Helper function to parse geometries from JSON
118+
GeometryObject? parseGeometry(Map<String, dynamic> json) {
119+
final type = json['type'];
120+
121+
switch (type) {
122+
case 'Point':
123+
return Point.fromJson(json);
124+
case 'LineString':
125+
return LineString.fromJson(json);
126+
case 'Polygon':
127+
return Polygon.fromJson(json);
128+
case 'MultiPoint':
129+
return MultiPoint.fromJson(json);
130+
case 'MultiLineString':
131+
return MultiLineString.fromJson(json);
132+
case 'MultiPolygon':
133+
return MultiPolygon.fromJson(json);
134+
case 'GeometryCollection':
135+
return GeometryCollection.fromJson(json);
136+
default:
137+
return null;
138+
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {
7+
"name": "Point Feature"
8+
},
9+
"geometry": {
10+
"type": "Point",
11+
"coordinates": [5.0, 10.0]
12+
}
13+
},
14+
{
15+
"type": "Feature",
16+
"properties": {
17+
"name": "Polygon Feature",
18+
"stroke": "#F00",
19+
"stroke-width": 2,
20+
"fill": "#F00",
21+
"fill-opacity": 0.3
22+
},
23+
"geometry": {
24+
"type": "Polygon",
25+
"coordinates": [
26+
[
27+
[-10.0, -10.0],
28+
[10.0, -10.0],
29+
[10.0, 10.0],
30+
[-10.0, 10.0],
31+
[-10.0, -10.0]
32+
]
33+
]
34+
}
35+
}
36+
]
37+
}

0 commit comments

Comments
 (0)