مرجع عملي منظّم بالمستويات: من تهيئة البيئة والأدوات، إلى أتمتة التحليل ونشر الخرائط والخدمات. جميع الأقسام تشرح الفكرة ثم خطوات التنفيذ ثم أمثلة كود قصيرة وواضحة.
الهدف: تجهيز بايثون، قراءة بيانات شبكية/خطية، تنفيذ عمليات شائعة، وتصدير النتائج.
# إنشاء بيئة
conda create -n gis python=3.11 -y
conda activate gis
# حزم شائعة
conda install -c conda-forge geopandas pyogrio rtree shapely fiona -y
conda install -c conda-forge rasterio rioxarray xarray -y
pip install matplotlib contextily rich tqdm
ملاحظة: على Windows، تأكد من تثبيت Microsoft C++ Build Tools عند الحاجة لبعض الحزم.
قراءة ملفات GeoPackage/GeoJSON/Shapefile، إصلاح CRS، حفظ نسخة نظيفة.
import geopandas as gpd
gdf = gpd.read_file("data/roads.gpkg", layer="primary_roads")
print(gdf.crs) # افحص نظام الإحداثيات
# تعريف CRS إذا كان مفقودًا
if gdf.crs is None:
gdf.set_crs(epsg=4326, inplace=True)
# تحويل مسقط إلى متري (UTM مثال)
gdf_m = gdf.to_crs(epsg=32638)
# حفظ
gdf_m.to_file("out/roads_clean.gpkg", layer="primary_roads", driver="GPKG")
قص طبقة على مضلع حدود، ثم تبسيط شكل هندسي لتخفيف الحجم.
study = gpd.read_file("data/study_boundary.geojson").to_crs(gdf_m.crs)
clipped = gpd.clip(gdf_m, study)
# تبسيط الشكل (يلزم CRS متري)
simple = clipped.copy()
simple["geometry"] = simple.geometry.simplify(tolerance=5)
simple.to_file("out/roads_clipped_simple.gpkg",
layer="roads", driver="GPKG")
فتح GeoTIFF شبكي وحساب إحصاءات سريعة.
import rasterio
from rasterio.plot import reshape_as_raster
import numpy as np
with rasterio.open("data/dem.tif") as src:
dem = src.read(1)
profile = src.profile
print(profile["crs"], profile["transform"])
print(np.nanmin(dem), np.nanmax(dem))
باستخدام rioxarray لاشتقاق انحدار تقريبي.
import rioxarray as rxr
import numpy as np
dem = rxr.open_rasterio("data/dem.tif").squeeze()
# تدرجات تقريبية (متر/بكسل)
dx = dem.differentiate("x")
dy = dem.differentiate("y")
slope = np.degrees(np.arctan(np.hypot(dx, dy)))
slope_da = dem.copy(data=slope)
slope_da.rio.to_raster("out/slope_deg.tif")
إذا أردت مؤشرات طبيعية مثل NDVI لاحقًا، ستجد نموذجًا جاهزًا في قسم المؤشرات أدناه ضمن بايثون.
ArcPy يعمل من بيئة ArcGIS Pro Python. مثال: قص طبقة، ثم Buffer، ثم حساب طول الطريق ضمن منطقة الدراسة.
import arcpy
arcpy.env.overwriteOutput = True
study = r"C:\gis\study.gdb\boundary"
roads = r"C:\gis\data.gdb\roads"
out_gdb = r"C:\gis\out.gdb"
clip_out = arcpy.analysis.Clip(roads, study, f"{out_gdb}\\roads_clip")
buf_out = arcpy.analysis.Buffer(clip_out, f"{out_gdb}\\roads_buf_50m", "50 Meters")
arcpy.management.AddField(clip_out, "len_m", "DOUBLE")
arcpy.management.CalculateGeometryAttributes(clip_out,
[["len_m","LENGTH_GEODESIC"]], length_unit="METERS")
print("done.")
نصيحة: احفظ التسلسل كـ ModelBuilder ثم صدّره ككود بايثون لتوثيق خطوات التحليل.
الهدف: بناء خريطة ويب قابلة للتضمين، إضافة طبقات أساسية، وإظهار بيانات GeoJSON، وتحكمات أساسية.
HTML بسيط مع Leaflet CDN. احفظ كملف مستقل لتجربة الفكرة.
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<div id="map" style="height:400px"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([24.7,46.7], 6);
const osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{attribution:'OSM'}).addTo(map);
fetch('data/points.geojson').then(r=>r.json()).then(g=>{
L.geoJSON(g,{
pointToLayer:(f,latlng)=>L.circleMarker(latlng,{radius:6,fillColor:"#6b4dff",color:"#89aaff",weight:1,fillOpacity:.9}),
onEachFeature:(f,layer)=>layer.bindPopup(f.properties.name||"")
}).addTo(map);
});
</script>
تبديل الطبقات الأساسية والتحكم في العرض.
const imagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
{attribution:'Esri'});
const topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
{attribution:'OpenTopo'});
const base = {"OSM":osm, "Imagery":imagery, "Topo":topo};
L.control.layers(base, null, {position:'topright'}).addTo(map);
مقتطف موجز لاستخدام ArcGIS Maps SDK for JS (نسخة 4.x).
<link rel="stylesheet" href="https://js.arcgis.com/4.29/esri/themes/light/main.css">
<div id="view" style="height:400px"></div>
<script src="https://js.arcgis.com/4.29/"></script>
<script>
require(["esri/Map","esri/views/MapView","esri/layers/FeatureLayer"],(Map,MapView,FeatureLayer)=>{
const map = new Map({basemap:"streets-navigation-vector"});
const view = new MapView({container:"view", map, center:[46.7,24.7], zoom:6});
const fl = new FeatureLayer({url:"YOUR/FeatureServer/0"});
map.add(fl);
});
</script>
يمكن ضبط الرموز والـ popups والفلترة من خصائص FeatureLayer.
الهدف: تنفيذ سلسلة مهام تلقائيًا، توليد خرائط وصور وملفات نهائية في مجلد واحد.
نموذج سكربت: يقرأ طبقة، يحسب طول الطرق لكل حي، يصدّر CSV وصورة بسيطة.
import geopandas as gpd
import matplotlib.pyplot as plt
districts = gpd.read_file("data/districts.gpkg").to_crs(epsg=32638)
roads = gpd.read_file("data/roads.gpkg").to_crs(districts.crs)
length = roads.length.groupby(roads.sindex.query_bulk(districts.geometry, predicate="intersects")[1]).sum()
districts["roads_km"] = (length.reindex(range(len(districts))).fillna(0) / 1000).values
districts[["name","roads_km"]].to_csv("out/roads_by_district.csv", index=False)
ax = districts.plot(column="roads_km", cmap="viridis", legend=True, figsize=(6,6))
plt.tight_layout(); plt.savefig("out/roads_map.png", dpi=180)
شغّليه يدويًا، أو عبر جدولة النظام (Task Scheduler/cron) لتنفيذ يومي/أسبوعي.
الهدف: إنشاء قاعدة مكانية، استيراد بيانات، فهارس مكانية، استعلامات شائعة وأداء جيد.
أوامر psql أولية:
CREATE DATABASE gisdb;
\c gisdb;
CREATE EXTENSION postgis;
-- تحقق
SELECT postgis_full_version();
استيراد Shapefile/GeoJSON عبر ogr2ogr:
ogr2ogr -f "PostgreSQL" PG:"dbname=gisdb user=postgres password=*" \
roads.shp -nln public.roads -nlt PROMOTE_TO_MULTI -lco GEOMETRY_NAME=geom -lco FID=gid -overwrite
يفضل التخزين بـ SRID مناسب (UTM لمنطقتك) لسهولة القياسات بالأمتار.
إنشاء فهرس GiST:
CREATE INDEX idx_roads_geom ON public.roads USING GIST(geom);
تقطيع المسار حسب حدود الدراسة:
CREATE TABLE roads_clip AS
SELECT r.*
FROM public.roads r
JOIN public.boundary b
ON ST_Intersects(r.geom, b.geom);
أطوال الطرق داخل كل حي:
SELECT d.id, d.name,
SUM(ST_Length(ST_Intersection(r.geom, d.geom)))/1000 AS km_len
FROM districts d
JOIN roads r
ON ST_Intersects(r.geom, d.geom)
GROUP BY d.id, d.name
ORDER BY km_len DESC;
| المهمة | الاستعلام |
|---|---|
| أقرب طريق لنقطة | |
| مضلعات التغطية لمسافات 5/10 دقائق (بافتراض سرعة) | |
الهدف: نشر طبقاتك كخدمات خرائط قياسية وربطها بتطبيقات الويب.
gisdb.عند الاستهلاك من JavaScript، استخدمي عناوين WMS/WFS/WMTS الرسمية وقيود CORS حسب المضيف.
<sld:StyledLayerDescriptor version="1.0.0"
xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc">
<sld:NamedLayer>
<sld:Name>roads</sld:Name>
<sld:UserStyle>
<sld:FeatureTypeStyle>
<sld:Rule>
<sld:LineSymbolizer>
<sld:Stroke>
<sld:CssParameter name="stroke">#6b4dff</sld:CssParameter>
<sld:CssParameter name="stroke-width">1.5</sld:CssParameter>
</sld:Stroke>
</sld:LineSymbolizer>
</sld:Rule>
</sld:FeatureTypeStyle>
</sld:UserStyle>
</sld:NamedLayer>
</sld:StyledLayerDescriptor>
الهدف: فهم سلسلة العمل: إعداد البيانات، تجزئة الصور إلى مربعات، تقسيم تدريب/تحقق، تدريب نموذج بسيط للتقسيم الدلالي.
import rasterio
from rasterio.windows import Window
from pathlib import Path
import numpy as np
src_path = "data/imagery.tif"
out_dir = Path("out/tiles"); out_dir.mkdir(parents=True, exist_ok=True)
tile = 512
with rasterio.open(src_path) as src:
for r in range(0, src.height, tile):
for c in range(0, src.width, tile):
w = Window(c, r, min(tile, src.width-c), min(tile, src.height-r))
arr = src.read(window=w)
if arr.size == 0: continue
profile = src.profile
profile.update({"height":arr.shape[1], "width":arr.shape[2],
"transform": rasterio.windows.transform(w, src.transform)})
out = out_dir / f"tile_{r}_{c}.tif"
with rasterio.open(out, "w", **profile) as dst:
dst.write(arr)
بعدها استخدم أداة تعليم (Labeling) لرسم الأقنعة، ثم قسم إلى train/val.
# ملاحظة: هذا مخطط هيكلي مبسط (ليس جاهزًا للإنتاج)
import torch, torch.nn as nn, torch.optim as optim
class MiniUNet(nn.Module):
def _init(self): super().init_()
# طبقات مختصرة جداً للعرض فقط
# dataset/dataloader لقراءة المربعات (X: صور، y: أقنعة)
# loop: train for N epochs, save best model
# الحفظ:
# torch.save(model.state_dict(), "out/model.pt")
في المشاريع الفعلية يُفضّل استخدام مكتبات جاهزة لـ Remote Sensing مثل torchgeo أو segmentation-models-pytorch مع ضبط Augmentations ومقاييس التقييم.
import rasterio
import numpy as np
with rasterio.open("data/S2_B08.tif") as nir, rasterio.open("data/S2_B04.tif") as red:
N = nir.read(1).astype("float32")
R = red.read(1).astype("float32")
ndvi = (N - R) / (N + R + 1e-6)
profile = nir.profile; profile.update(dtype="float32")
with rasterio.open("out/ndvi.tif","w",**profile) as dst:
dst.write(ndvi, 1)
const wms = L.tileLayer.wms("https://your-geoserver/geoserver/wms",{
layers:"workspace:roads",
format:"image/png",
transparent:true
}).addTo(map);
SELECT id, ST_PointOnSurface(geom) AS label_pt
INTO labels
FROM parcels;