Skip to content

Commit ba31318

Browse files
committed
data: add Ghana and Rwanda-Uganda agriculture layer configs
Add raster configs for Ghana crop type and 5 meteorological layers (precipitation, temperature, wind speed, solar radiation, soil moisture). Add vector config for Rwanda maize crop map, animated config for Uganda dry matter productivity, and raster config for Uganda yield estimation. Add SLD-to-QML conversion helper script.
1 parent ed56441 commit ba31318

4 files changed

Lines changed: 358 additions & 0 deletions

File tree

data-processing/src/animated_config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,18 @@ layers:
476476
2: "#006724"
477477
3: "#f2d33a"
478478
4: "#e96a1f"
479+
480+
# Rwanda-Uganda - Agriculture
481+
uganda_dry_matter_productivity:
482+
input_folder: "../data/raw/Agriculture/Rwanda-Uganda/QGIS_layers/3_Dry-Matter-Productivity/Uganda-kassnada-Mityana-districts/"
483+
output_folder: "../data/processed/Agriculture/Rwanda-Uganda/APNGs/DryMatterProductivity/"
484+
min_z: 8
485+
max_z: 12
486+
vmin: 0
487+
vmax: 40
488+
date_format: "YYYYMMDD"
489+
colormap:
490+
type: binned
491+
intervals: [4, 8, 12, 16, 20, 24, 28, 32, 36, 40]
492+
colors: ["#96714e", "#f4b982", "#d6e2b3", "#b5d178", "#4a8131", "#264f24", "#0f330d", "#041903", "#a258bf", "#622573"]
493+
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
"""Convert OGC SLD raster ColorMap files to QGIS QML format.
2+
3+
Usage:
4+
python sld_to_qml.py input.sld # writes input.qml next to input.sld
5+
python sld_to_qml.py input.sld output.qml # explicit output path
6+
python sld_to_qml.py *.sld # batch convert all SLDs in a folder
7+
"""
8+
9+
import argparse
10+
import xml.etree.ElementTree as ET
11+
from pathlib import Path
12+
13+
14+
def parse_sld(sld_path):
15+
"""Extract color map entries from an SLD file."""
16+
tree = ET.parse(sld_path)
17+
root = tree.getroot()
18+
entries = []
19+
for entry in root.iter("{http://www.opengis.net/sld}ColorMapEntry"):
20+
color = entry.get("color")
21+
quantity = entry.get("quantity")
22+
label = entry.get("label", "")
23+
entries.append((float(quantity), color, label))
24+
return entries
25+
26+
27+
def hex_to_rgba_str(hex_color):
28+
"""Convert #RRGGBB to QGIS 'R,G,B,255,rgb:r,g,b,1' format."""
29+
h = hex_color.lstrip("#")
30+
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
31+
rf, gf, bf = r / 255.0, g / 255.0, b / 255.0
32+
return f"{r},{g},{b},255,rgb:{rf},{gf},{bf},1"
33+
34+
35+
def build_gradient_stops(entries):
36+
"""Build QGIS gradient stops string from color entries."""
37+
if len(entries) < 3:
38+
return ""
39+
min_val = entries[0][0]
40+
max_val = entries[-1][0]
41+
val_range = max_val - min_val
42+
if val_range == 0:
43+
return ""
44+
stops = []
45+
for val, color, _label in entries[1:-1]:
46+
pos = (val - min_val) / val_range
47+
rgba = hex_to_rgba_str(color)
48+
stops.append(f"{pos:.6f};{rgba};rgb;ccw")
49+
return ":".join(stops)
50+
51+
52+
def generate_qml(entries, output_path):
53+
"""Generate a QGIS QML file from SLD color map entries."""
54+
min_val = entries[0][0]
55+
max_val = entries[-1][0]
56+
color1_rgba = hex_to_rgba_str(entries[0][1])
57+
color2_rgba = hex_to_rgba_str(entries[-1][1])
58+
stops_str = build_gradient_stops(entries)
59+
60+
item_lines = []
61+
for val, color, label in entries:
62+
item_lines.append(
63+
f' <item color="{color}" value="{val}" alpha="255" label="{label}"/>'
64+
)
65+
items_xml = "\n".join(item_lines)
66+
67+
stops_option = ""
68+
if stops_str:
69+
stops_option = f'\n <Option name="stops" value="{stops_str}" type="QString"/>'
70+
71+
qml = f'''<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
72+
<qgis version="3.40.5-Bratislava" hasScaleBasedVisibilityFlag="0" styleCategories="AllStyleCategories" autoRefreshMode="Disabled" autoRefreshTime="0" maxScale="0" minScale="1e+08">
73+
<flags>
74+
<Identifiable>1</Identifiable>
75+
<Removable>1</Removable>
76+
<Searchable>1</Searchable>
77+
<Private>0</Private>
78+
</flags>
79+
<temporal mode="0" enabled="0" fetchMode="0" bandNumber="1">
80+
<fixedRange>
81+
<start></start>
82+
<end></end>
83+
</fixedRange>
84+
</temporal>
85+
<elevation mode="RepresentsElevationSurface" enabled="0" zscale="1" band="1" symbology="Line" zoffset="0">
86+
<data-defined-properties>
87+
<Option type="Map">
88+
<Option type="QString" value="" name="name"/>
89+
<Option name="properties"/>
90+
<Option type="QString" value="collection" name="type"/>
91+
</Option>
92+
</data-defined-properties>
93+
<profileLineSymbol>
94+
<symbol alpha="1" force_rhr="0" type="line" frame_rate="10" clip_to_extent="1" is_animated="0" name="">
95+
<data_defined_properties>
96+
<Option type="Map">
97+
<Option type="QString" value="" name="name"/>
98+
<Option name="properties"/>
99+
<Option type="QString" value="collection" name="type"/>
100+
</Option>
101+
</data_defined_properties>
102+
<layer class="SimpleLine" pass="0" id="{{{{auto}}}}" enabled="1" locked="0">
103+
<Option type="Map">
104+
<Option type="QString" value="0" name="align_dash_pattern"/>
105+
<Option type="QString" value="square" name="capstyle"/>
106+
<Option type="QString" value="5;2" name="customdash"/>
107+
<Option type="QString" value="3x:0,0,0,0,0,0" name="customdash_map_unit_scale"/>
108+
<Option type="QString" value="MM" name="customdash_unit"/>
109+
<Option type="QString" value="0" name="dash_pattern_offset"/>
110+
<Option type="QString" value="3x:0,0,0,0,0,0" name="dash_pattern_offset_map_unit_scale"/>
111+
<Option type="QString" value="MM" name="dash_pattern_offset_unit"/>
112+
<Option type="QString" value="0" name="draw_inside_polygon"/>
113+
<Option type="QString" value="bevel" name="joinstyle"/>
114+
<Option type="QString" value="141,90,153,255,rgb:0.55294117647058827,0.35294117647058826,0.59999999999999998,1" name="line_color"/>
115+
<Option type="QString" value="solid" name="line_style"/>
116+
<Option type="QString" value="0.6" name="line_width"/>
117+
<Option type="QString" value="MM" name="line_width_unit"/>
118+
<Option type="QString" value="0" name="offset"/>
119+
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
120+
<Option type="QString" value="MM" name="offset_unit"/>
121+
<Option type="QString" value="0" name="ring_filter"/>
122+
<Option type="QString" value="0" name="trim_distance_end"/>
123+
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_end_map_unit_scale"/>
124+
<Option type="QString" value="MM" name="trim_distance_end_unit"/>
125+
<Option type="QString" value="0" name="trim_distance_start"/>
126+
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_start_map_unit_scale"/>
127+
<Option type="QString" value="MM" name="trim_distance_start_unit"/>
128+
<Option type="QString" value="0" name="tweak_dash_pattern_on_corners"/>
129+
<Option type="QString" value="0" name="use_custom_dash"/>
130+
<Option type="QString" value="3x:0,0,0,0,0,0" name="width_map_unit_scale"/>
131+
</Option>
132+
<data_defined_properties>
133+
<Option type="Map">
134+
<Option type="QString" value="" name="name"/>
135+
<Option name="properties"/>
136+
<Option type="QString" value="collection" name="type"/>
137+
</Option>
138+
</data_defined_properties>
139+
</layer>
140+
</symbol>
141+
</profileLineSymbol>
142+
<profileFillSymbol>
143+
<symbol alpha="1" force_rhr="0" type="fill" frame_rate="10" clip_to_extent="1" is_animated="0" name="">
144+
<data_defined_properties>
145+
<Option type="Map">
146+
<Option type="QString" value="" name="name"/>
147+
<Option name="properties"/>
148+
<Option type="QString" value="collection" name="type"/>
149+
</Option>
150+
</data_defined_properties>
151+
<layer class="SimpleFill" pass="0" id="{{{{auto}}}}" enabled="1" locked="0">
152+
<Option type="Map">
153+
<Option type="QString" value="3x:0,0,0,0,0,0" name="border_width_map_unit_scale"/>
154+
<Option type="QString" value="141,90,153,255,rgb:0.55294117647058827,0.35294117647058826,0.59999999999999998,1" name="color"/>
155+
<Option type="QString" value="bevel" name="joinstyle"/>
156+
<Option type="QString" value="0,0" name="offset"/>
157+
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
158+
<Option type="QString" value="MM" name="offset_unit"/>
159+
<Option type="QString" value="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1" name="outline_color"/>
160+
<Option type="QString" value="no" name="outline_style"/>
161+
<Option type="QString" value="0.26" name="outline_width"/>
162+
<Option type="QString" value="MM" name="outline_width_unit"/>
163+
<Option type="QString" value="solid" name="style"/>
164+
</Option>
165+
<data_defined_properties>
166+
<Option type="Map">
167+
<Option type="QString" value="" name="name"/>
168+
<Option name="properties"/>
169+
<Option type="QString" value="collection" name="type"/>
170+
</Option>
171+
</data_defined_properties>
172+
</layer>
173+
</symbol>
174+
</profileFillSymbol>
175+
</elevation>
176+
<customproperties>
177+
<Option type="Map">
178+
<Option type="bool" value="false" name="WMSBackgroundLayer"/>
179+
<Option type="bool" value="false" name="WMSPublishDataSourceUrl"/>
180+
<Option type="int" value="0" name="embeddedWidgets/count"/>
181+
<Option type="QString" value="Value" name="identify/format"/>
182+
</Option>
183+
</customproperties>
184+
<mapTip enabled="1"></mapTip>
185+
<pipe-data-defined-properties>
186+
<Option type="Map">
187+
<Option type="QString" value="" name="name"/>
188+
<Option name="properties"/>
189+
<Option type="QString" value="collection" name="type"/>
190+
</Option>
191+
</pipe-data-defined-properties>
192+
<pipe>
193+
<provider>
194+
<resampling zoomedOutResamplingMethod="nearestNeighbour" maxOversampling="2" zoomedInResamplingMethod="nearestNeighbour" enabled="false"/>
195+
</provider>
196+
<rasterrenderer classificationMin="{min_val}" classificationMax="{max_val}" nodataColor="" type="singlebandpseudocolor" band="1" alphaBand="-1" opacity="1">
197+
<rasterTransparency/>
198+
<minMaxOrigin>
199+
<limits>None</limits>
200+
<extent>WholeRaster</extent>
201+
<statAccuracy>Estimated</statAccuracy>
202+
<cumulativeCutLower>0.02</cumulativeCutLower>
203+
<cumulativeCutUpper>0.98</cumulativeCutUpper>
204+
<stdDevFactor>2</stdDevFactor>
205+
</minMaxOrigin>
206+
<rastershader>
207+
<colorrampshader clip="0" colorRampType="INTERPOLATED" minimumValue="{min_val}" maximumValue="{max_val}" classificationMode="1" labelPrecision="4">
208+
<colorramp type="gradient" name="[source]">
209+
<Option type="Map">
210+
<Option type="QString" value="{color1_rgba}" name="color1"/>
211+
<Option type="QString" value="{color2_rgba}" name="color2"/>
212+
<Option type="QString" value="ccw" name="direction"/>
213+
<Option type="QString" value="0" name="discrete"/>
214+
<Option type="QString" value="gradient" name="rampType"/>
215+
<Option type="QString" value="rgb" name="spec"/>{stops_option}
216+
</Option>
217+
</colorramp>
218+
{items_xml}
219+
<rampLegendSettings suffix="" direction="0" prefix="" maximumLabel="" useContinuousLegend="1" minimumLabel="" orientation="2">
220+
<numericFormat id="basic">
221+
<Option type="Map">
222+
<Option name="decimal_separator" type="invalid"/>
223+
<Option name="decimals" value="6" type="int"/>
224+
<Option name="rounding_type" value="0" type="int"/>
225+
<Option name="show_plus" value="false" type="bool"/>
226+
<Option name="show_thousand_separator" value="true" type="bool"/>
227+
<Option name="show_trailing_zeros" value="false" type="bool"/>
228+
<Option name="thousand_separator" type="invalid"/>
229+
</Option>
230+
</numericFormat>
231+
</rampLegendSettings>
232+
</colorrampshader>
233+
</rastershader>
234+
</rasterrenderer>
235+
<brightnesscontrast gamma="1" contrast="0" brightness="0"/>
236+
<huesaturation colorizeGreen="128" colorizeStrength="100" invertColors="0" saturation="0" colorizeOn="0" colorizeBlue="128" grayscaleMode="0" colorizeRed="255"/>
237+
<rasterresampler maxOversampling="2"/>
238+
<resamplingStage>resamplingFilter</resamplingStage>
239+
</pipe>
240+
<blendMode>0</blendMode>
241+
</qgis>
242+
'''
243+
Path(output_path).write_text(qml)
244+
print(f"Created: {output_path} ({len(entries)} color stops)")
245+
246+
247+
def main():
248+
parser = argparse.ArgumentParser(
249+
description="Convert OGC SLD raster ColorMap files to QGIS QML format."
250+
)
251+
parser.add_argument("sld_files", nargs="+", type=Path, help="SLD file(s) to convert")
252+
parser.add_argument(
253+
"-o",
254+
"--output",
255+
type=Path,
256+
default=None,
257+
help="Output QML path (only valid with a single input file)",
258+
)
259+
args = parser.parse_args()
260+
261+
if args.output and len(args.sld_files) > 1:
262+
parser.error("-o/--output can only be used with a single input file")
263+
264+
for sld_path in args.sld_files:
265+
if not sld_path.exists():
266+
print(f"SKIP (not found): {sld_path}")
267+
continue
268+
qml_path = args.output if args.output else sld_path.with_suffix(".qml")
269+
entries = parse_sld(sld_path)
270+
if not entries:
271+
print(f"SKIP (no ColorMapEntry found): {sld_path}")
272+
continue
273+
generate_qml(entries, qml_path)
274+
275+
276+
if __name__ == "__main__":
277+
main()

data-processing/src/raster_config.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,4 +786,62 @@ layers:
786786
style_file: "SOC_change.qml"
787787
output_file: "../data/processed/Agriculture/India/Rasters/India_SOC_Change.tif"
788788
layer_name: "India_SOC_Change"
789+
max_zoom: 14
790+
791+
# Ghana - Agriculture
792+
ghana_crop_type:
793+
base_path: "../data/raw/Agriculture/Ghana/Ghana/CropType"
794+
input_file: "openEO_Tono IRRIGABLE AREA_2023_WRLDCOV_CROP_CLIP_postproc_DEFthr_clip.tif"
795+
style_file: "Crop_Type_Style.qml"
796+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_CropType.tif"
797+
layer_name: "Ghana_CropType"
798+
max_zoom: 14
799+
800+
ghana_precipitation:
801+
base_path: "../data/raw/Agriculture/Ghana/Ghana/Country_Met"
802+
input_file: "Precipitation_Country_1981-2010.tif"
803+
style_file: "Precipitation_Country_1981-2010.qml"
804+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_Precipitation.tif"
805+
layer_name: "Ghana_Precipitation"
806+
max_zoom: 9
807+
808+
ghana_temperature:
809+
base_path: "../data/raw/Agriculture/Ghana/Ghana/Country_Met"
810+
input_file: "Temperature_Country_1981-2010.tif"
811+
style_file: "Temperature_Country_1981-2010.qml"
812+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_Temperature.tif"
813+
layer_name: "Ghana_Temperature"
814+
max_zoom: 9
815+
816+
ghana_wind_speed:
817+
base_path: "../data/raw/Agriculture/Ghana/Ghana/Country_Met"
818+
input_file: "WindSpeed_Country_1981-2010.tif"
819+
style_file: "WindSpeed_Country_1981-2010.qml"
820+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_WindSpeed.tif"
821+
layer_name: "Ghana_WindSpeed"
822+
max_zoom: 9
823+
824+
ghana_solar_radiation:
825+
base_path: "../data/raw/Agriculture/Ghana/Ghana/Country_Met"
826+
input_file: "SolarRadiation_Country_1981-2010.tif"
827+
style_file: "SolarRadiation_Country_1981-2010.qml"
828+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_SolarRadiation.tif"
829+
layer_name: "Ghana_SolarRadiation"
830+
max_zoom: 9
831+
832+
ghana_soil_moisture:
833+
base_path: "../data/raw/Agriculture/Ghana/Ghana/Country_Met"
834+
input_file: "SoilMoisture_Country_1981-2010.tif"
835+
style_file: "SoilMoisture_Country_1981-2010.qml"
836+
output_file: "../data/processed/Agriculture/Ghana/Rasters/Ghana_SoilMoisture.tif"
837+
layer_name: "Ghana_SoilMoisture"
838+
max_zoom: 9
839+
840+
# Rwanda-Uganda - Agriculture
841+
uganda_yield_estimation:
842+
base_path: "../data/raw/Agriculture/Rwanda-Uganda/QGIS_layers/4_Yield-estimation"
843+
input_file: "Uganda-kassnada-Mityana-districts/GDA-AGRI_UC8-Uganda_Kassanda-district_maize_Yield-Estimation_20230301-20230830_10m.tif"
844+
style_file: "yield_style.qml"
845+
output_file: "../data/processed/Agriculture/Rwanda-Uganda/Rasters/Uganda_YieldEstimation.tif"
846+
layer_name: "Uganda_YieldEstimation"
789847
max_zoom: 14

data-processing/src/vector_config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,11 @@ layers:
453453
min_zoom: 3
454454
max_zoom: 9
455455

456+
# Rwanda-Uganda - Agriculture
457+
rwanda_maize_crop_map:
458+
input_file: "../data/raw/Agriculture/Rwanda-Uganda/QGIS_layers/2_Crop-mapping/Rwanda-AOI3/GDA-AGRI_UC8-Rwanda_AOI3_maize-map_20230301-20230831_total.shp"
459+
output_file: "../data/processed/Agriculture/Rwanda-Uganda/Vectors/Rwanda_MaizeCropMap.mbtiles"
460+
layer_name: "Rwanda_MaizeCropMap"
461+
min_zoom: 6
462+
max_zoom: 14
463+

0 commit comments

Comments
 (0)