Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

GeoJSON

إدخال إخراج اسم مستعار

الوصف

تُتبادَل بيانات GeoJSON في صورة مستند FeatureCollection واحد، ويحوّله ClickHouse إلى ثلاثة أعمدة — id وgeometry وproperties — بحيث تمثل كل مجموعة عنصر Feature واحدًا. تؤدي قراءة المستند إلى إنتاج صف واحد لكل Feature؛ بينما تؤدي الكتابة إلى إنتاج Feature واحد لكل صف.

قراءة البيانات

تنتج قراءة FeatureCollection صفًا واحدًا لكل معلم وفق المخطط الثابت التالي:

Column Type Description
id Nullable(String) عنصر id الخاص بالمعلم (سلسلة JSON أو رقم)، ويُخزَّن كنص؛ وتكون القيمة NULL إذا كان id غير موجود أو كانت قيمته null، بينما يُحتفَظ بمعرّف السلسلة الفارغة الصريح على هيئة ''.
geometry Geometry هندسة المعلم، وتُخزَّن كنوع Variant من Geometry.
properties Nullable(JSON) كائن properties الخاص بالمعلم، ويُخزَّن في عمود JSON شبه منظَّم. وتُحفَظ القيمة الصريحة "properties": null على هيئة NULL.

تُخزَّن كل هندسة في النوع Geometry الخاص بـ ClickHouse (وهو Variant). أنواع هندسة GeoJSON المدعومة هي Point وMultiPoint وLineString وMultiLineString وPolygon وMultiPolygon. أما نوع هندسة GeoJSON المتبقي، GeometryCollection، فلا يمكن تمثيله بالنوع Geometry؛ وقراءته في العمود geometry تؤدي افتراضيًا إلى ظهور استثناء، ويمكن تغيير هذا السلوك لإدراج NULL بدلًا من ذلك — انظر التعامل مع أنواع الهندسة غير المدعومة أدناه. افتراضيًا، تكون قيمة العمود geometry هي NULL فقط عندما تكون هندسة المعلم قيمة null صريحة في JSON؛ ومع input_format_geojson_unsupported_geometry_handling = 'null' تكون أيضًا NULL عند استخدام نوع هندسة غير مدعوم.

يُتحقَّق من بنية المستند: يجب أن تكون قيمة type في المستوى الأعلى هي FeatureCollection، ويجب أن يكون لكل عنصر في features قيمة type تساوي Feature. افتراضيًا، يجب أن تستوفي الإحداثيات شروط صحة شكل GeoJSON — إذ يجب أن يحتوي LineString (وكل خط في MultiLineString) على نقطتين على الأقل، ويجب أن تكون حلقة Polygon (وكل حلقة في MultiPolygon) مغلقة وأن تحتوي على أربع نقاط على الأقل (انظر التحقق من صحة الهندسة). تُرفَض المستندات غير السليمة بدلًا من تحميلها بصمت.

ترتيب المفاتيح مرن: يمكن أن تظهر type في المستوى الأعلى قبل مصفوفة features أو بعدها، وداخل كائن هندسي يمكن أن تظهر coordinates قبل type أو بعدها.

يعيد استنتاج المخطط المخطط الثابت أعلاه، لذا يعمل DESCRIBE وSELECT ... FROM format(...) دون الحاجة إلى تعريف جدول.

بالنظر إلى ملف GeoJSON التالي london.geojson، الذي يحتوي على مزيج من أنواع الهندسة:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "id": "1",
            "geometry": {"type": "Point", "coordinates": [-0.0761, 51.5081]},
            "properties": {"name": "Tower of London", "feature_type": "landmark", "year_built": 1078}
        },
        {
            "type": "Feature",
            "id": "2",
            "geometry": {
                "type": "LineString",
                "coordinates": [[-0.2500, 51.4700], [-0.1800, 51.4900], [-0.1200, 51.5060], [-0.0700, 51.5050], [0.0000, 51.5100]]
            },
            "properties": {"name": "River Thames", "feature_type": "river", "length_km": 346}
        },
        {
            "type": "Feature",
            "id": "3",
            "geometry": {
                "type": "Polygon",
                "coordinates": [[[-0.1880, 51.5074], [-0.1533, 51.5074], [-0.1533, 51.5153], [-0.1880, 51.5153], [-0.1880, 51.5074]]]
            },
            "properties": {"name": "Hyde Park", "feature_type": "park", "area_km2": 1.42}
        }
    ]
}

يمكننا الاستعلام عن الملف وفحص الأنواع الهندسية:

Querysql
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson', GeoJSON);
Responseresponse
┌─id─┬─name────────────┬─geo_type───┐
│ 1  │ Tower of London │ Point      │
│ 2  │ River Thames    │ LineString │
│ 3  │ Hyde Park       │ Polygon    │
└────┴─────────────────┴────────────┘

يُكتشَف امتداد الملف .geojson تلقائيًا، لذا يمكن حذف وسيط التنسيق:

Querysql
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson');

يمكننا استخدام variantType للتحقق من النوع الفعلي لكل كائن Geometry:

Querysql
SELECT properties.name AS name, geometry, variantType(geometry)
FROM file('london.geojson', GeoJSON);
Responseresponse
Row 1:
──────
name:                  Tower of London
geometry:              (-0.0761,51.5081)
variantType(geometry): Point

Row 2:
──────
name:                  River Thames
geometry:              [(-0.25,51.47),(-0.18,51.49),(-0.12,51.506),(-0.07,51.505),(0,51.51)]
variantType(geometry): LineString

Row 3:
──────
name:                  Hyde Park
geometry:              [[(-0.188,51.5074),(-0.1533,51.5074),(-0.1533,51.5153),(-0.188,51.5153),(-0.188,51.5074)]]
variantType(geometry): Polygon

ويمكننا استخراج البيانات الأساسية كما يلي:

Querysql
SELECT properties.name AS name, variantType(geometry), geometry.Point, geometry.LineString, geometry.Polygon
FROM file('london.geojson', GeoJSON);
Responseresponse
Row 1:
──────
name:                  Tower of London
variantType(geometry): Point
geometry.Point:        (-0.0761,51.5081)
geometry.LineString:   []
geometry.Polygon:      []

Row 2:
──────
name:                  River Thames
variantType(geometry): LineString
geometry.Point:        (0,0)
geometry.LineString:   [(-0.25,51.47),(-0.18,51.49),(-0.12,51.506),(-0.07,51.505),(0,51.51)]
geometry.Polygon:      []

Row 3:
──────
name:                  Hyde Park
variantType(geometry): Polygon
geometry.Point:        (0,0)
geometry.LineString:   []
geometry.Polygon:      [[(-0.188,51.5074),(-0.1533,51.5074),(-0.1533,51.5153),(-0.188,51.5153),(-0.188,51.5074)]]

يؤدي الوصول إلى عمود فرعي من نوع Geometry إلى إرجاع القيمة عندما يحتوي الصف على ذلك النوع، وإلا فتُرجَع القيمة الافتراضية لذلك النوع — (0,0) لـ Point و[] للأنواع المستندة إلى المصفوفات — لذا استخدم variantType(geometry) لمعرفة النوع المعيَّن.

يمكننا أيضًا إدخال بيانات GeoJSON إلى جدول:

Querysql
CREATE TABLE london
(
    id           String,
    geometry     Geometry,
    properties   Nullable(JSON),
    name         String MATERIALIZED properties.name,
    feature_type String MATERIALIZED properties.feature_type
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO london
SELECT id, geometry, properties
FROM file('london.geojson', GeoJSON);

ثم نفّذ استعلامًا حسب نوع المعلم:

Querysql
SELECT name, feature_type, variantType(geometry) AS geo_type
FROM london
ORDER BY id;
Responseresponse
┌─name────────────┬─feature_type─┬─geo_type───┐
│ Tower of London │ landmark     │ Point      │
│ River Thames    │ river        │ LineString │
│ Hyde Park       │ park         │ Polygon    │
└─────────────────┴──────────────┴────────────┘

يمكننا أيضًا استنتاج مخطط بيانات GeoJSON من دون تعريف جدول:

Querysql
DESCRIBE format(GeoJSON, '{"type":"FeatureCollection","features":[]}');
Responseresponse
┌─name───────┬─type─────────────┐
│ id         │ Nullable(String) │
│ geometry   │ Geometry         │
│ properties │ Nullable(JSON)   │
└────────────┴──────────────────┘

التعامل مع أنواع هندسة غير المدعومة

بعض أنواع الهندسة الصالحة في GeoJSON — مثل GeometryCollection — لا يمكن تمثيلها بالنوع Geometry في ClickHouse. يمكنك التحكم في ما يحدث عندما يلزم تخزين هندسة من هذا النوع في العمود geometry باستخدام الإعداد input_format_geojson_unsupported_geometry_handling. القيم الممكنة هي:

  • 'throw' — طرح استثناء (الافتراضي)
  • 'null' — إدراج قيمة NULL في العمود geometry ومتابعة التحليل

ينطبق هذا السلوك فقط عند قراءة العمود geometry. وعندما لا يكون geometry عمود إخراج مطلوبًا (على سبيل المثال SELECT id FROM ...)، تظل الهندسة غير المدعومة خاضعة للتحقق من صحة البنية، لكنها لا تؤدي إلى تطبيق هذا السلوك — فلا يُطرح استثناء ولا تُدرج قيمة NULL، لأنه لا تتم مادية أي قيمة هندسة.

القيود

تعكس عملية القراءة فقط ما يلائم المخطط الثابت، لذلك لا تُحفَظ بعض معلومات GeoJSON:

  • لا يُنتَج سوى id وgeometry وproperties؛ ولا تظهر بنية المستند الأخرى على شكل أعمدة.
  • يُسقَط الإحداثي الثالث (الارتفاع) للموضع، وكذلك أي إحداثيات تليه — فتصبح المواضع [longitude, latitude].
  • يتم تجاهل bbox والأعضاء الخارجية (مثل name أو crs على المستوى الأعلى، أو الأعضاء الإضافيين داخل Feature).
  • يُخزَّن id الرقمي كنص، لذلك يضيع الفرق بين string وnumber؛ أما id غير الموجود أو الذي قيمته null فيصبح NULL.
  • لا يمكن تمثيل GeometryCollection — راجع التعامل مع أنواع هندسة غير المدعومة.

كتابة البيانات

يؤدي كتابة مجموعة النتائج إلى إنتاج FeatureCollection واحدة بتنسيق GeoJSON وفقًا للمواصفة FeatureCollection، بحيث يقابل كل صف عنصر Feature واحد.

تُربَط أعمدة النتيجة بكل Feature كما يلي:

عضو Feature يُنشأ من ملاحظات
type تكون دائمًا "Feature".
geometry العمود الهندسي الوحيد يجب أن يوجد عمود هندسي واحد فقط، وإلا فسيُرفَض الاستعلام. وتُكتَب قيمة NULL الهندسية على هيئة null.
id عمود باسم id يُحذَف إذا كانت القيمة NULL. ويُكتَب عمود String كسلسلة JSON، بينما يُكتَب العمود الرقمي كرقم JSON.
properties جميع الأعمدة المتبقية إذا وُجد عمود واحد باسم properties وكان نوعه شبيهًا بالكائن (JSON أو Map أو Tuple مسمّى)، فتُكتَب قيمته مباشرةً بوصفها كائن properties بدلًا من تضمينها تحت المفتاح properties. وبخلاف ذلك، يصبح كل عمود متبقٍ خاصيةً يكون مفتاحها اسمه (ويكون الناتج كائنًا فارغًا إذا لم توجد أعمدة متبقية).

يمكن أن يكون العمود الهندسي من النوع Geometry أو من نوع جغرافي محدد؛ ويُطابَق كلٌّ منها مع نوع هندسي في GeoJSON كما يلي:

نوع ClickHouse قيمة "type" في GeoJSON
Point Point
MultiPoint MultiPoint
LineString LineString
MultiLineString MultiLineString
Polygon Polygon
MultiPolygon MultiPolygon
Ring Polygon (حلقة واحدة)
Geometry نوع المتغير النشط (أو null)

لا يُعَد Ring نوعًا هندسيًا في GeoJSON — إذ إن الحلقة الخطية هي أحد مكونات Polygon — لذلك تُكتَب قيمة Ring على هيئة Polygon ذي حلقة واحدة.

أمثلة

بمتابعة استخدام جدول london الذي أُنشئ أعلاه، فإن تصدير أعمدة السمات العادية يحوّل كل عمود، باستثناء id وgeometry، إلى خاصية:

Querysql
SELECT id, geometry, name, feature_type
FROM london
ORDER BY id
FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","id":"1","geometry":{"type":"Point","coordinates":[-0.0761,51.5081]},"properties":{"name":"Tower of London","feature_type":"landmark"}},{"type":"Feature","id":"2","geometry":{"type":"LineString","coordinates":[[-0.25,51.47],[-0.18,51.49],[-0.12,51.506],[-0.07,51.505],[0,51.51]]},"properties":{"name":"River Thames","feature_type":"river"}},{"type":"Feature","id":"3","geometry":{"type":"Polygon","coordinates":[[[-0.188,51.5074],[-0.1533,51.5074],[-0.1533,51.5153],[-0.188,51.5153],[-0.188,51.5074]]]},"properties":{"name":"Hyde Park","feature_type":"park"}}]}

نظرًا لأن عمودًا واحدًا فقط من النوع object باسم properties يُكتب مباشرةً، فإن قراءة ملف GeoJSON ثم كتابته مرةً أخرى كما هو تعيد إنتاج الوثيقة نفسها (إذ إن الأعمدة id وgeometry وproperties هي الأعمدة التي جرى استنتاجها للملف):

Querysql
SELECT * FROM file('london.geojson', GeoJSON) FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","id":"1","geometry":{"type":"Point","coordinates":[-0.0761,51.5081]},"properties":{"feature_type":"landmark","name":"Tower of London","year_built":1078}},{"type":"Feature","id":"2","geometry":{"type":"LineString","coordinates":[[-0.25,51.47],[-0.18,51.49],[-0.12,51.506],[-0.07,51.505],[0,51.51]]},"properties":{"feature_type":"river","length_km":346,"name":"River Thames"}},{"type":"Feature","id":"3","geometry":{"type":"Polygon","coordinates":[[[-0.188,51.5074],[-0.1533,51.5074],[-0.1533,51.5153],[-0.188,51.5153],[-0.188,51.5074]]]},"properties":{"area_km2":1.42,"feature_type":"park","name":"Hyde Park"}}]}

يُكتَب العمود الرقمي id كرقم JSON (ويُحذَف id من النوع Nullable بالكامل إذا كانت قيمته NULL):

Querysql
SELECT 42 AS id, (-0.1276, 51.5072)::Point AS geometry FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"geometry":{"type":"Point","coordinates":[-0.1276,51.5072]},"properties":{}}]}

تُكتب Ring على شكل Polygon أحادي الحلقة:

Querysql
SELECT [(0., 0.), (10., 0.), (10., 10.), (0., 0.)]::Ring AS geometry FORMAT GeoJSON;
Responseresponse
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,0]]]},"properties":{}}]}

الكتابة إلى ملف

استخدم INTO OUTFILE لكتابة ملف GeoJSON من جهة العميل:

Querysql
SELECT id, geometry, properties
FROM london
ORDER BY id
INTO OUTFILE 'london_export.geojson'
FORMAT GeoJSON;

يمكن للخادم نفسه كتابة الملف باستخدام دالة الجدول file (يختار الامتداد .geojson التنسيق تلقائيًا):

Querysql
INSERT INTO FUNCTION file('london_export.geojson', GeoJSON)
SELECT id, geometry, properties FROM london;

القيود

يعكس الإخراج فقط ما يخزّنه ClickHouse:

  • لا يمكن إعادة إنتاج المعلومات التي تُفقَد عند القراءة — مثل ارتفاع الموضع، وbbox، والأعضاء الخارجية، والتمييز في id بين كونه سلسلة نصية أو رقمًا — راجع قيود القراءة.
  • تُكتَب الإحداثيات من قيم Float64 باستخدام أقصر تمثيل يضمن استعادتها كما هي.
  • يُخرَج كائن properties المأخوذ مباشرةً من عمود JSON وفق ترتيب المفاتيح القياسي لنوع JSON، وقد يختلف ذلك عن الإدخال.

تُكتَب الأشكال الهندسية تمامًا كما هي مخزنة، مع الحفاظ على ترتيب الإحداثيات واتجاه الالتفاف. افتراضيًا، يُفرَض التحقق من صحة شكل GeoJSON عند الكتابة (راجع Geometry validation): فيُرفَض أي شكل هندسي غير صالح كهيئة GeoJSON، مثل LineString يحتوي على نقطة واحدة أو حلقة Polygon غير مغلقة، بحيث يمكن قراءة المستند المكتوب مجددًا. عيّن format_geojson_validate_geometry = 0 لإخراج هذه الأشكال الهندسية كما هي بدلًا من ذلك، مما ينتج GeoJSON صحيحًا من حيث البنية لكنه غير مطابق للمواصفة. ولا يُفرَض أيضًا قيد قاعدة اليد اليمنى (اتجاه الالتفاف) في أي من الحالتين، كما يُحفَظ التمييز بين null وكائن properties الفارغ.

التحقق من صحة الهندسة

يتحكم الإعداد format_geojson_validate_geometry في ما إذا كانت الصيغة تفرض قواعد شكل Geometry وفقًا لـ RFC 7946، في كلا الاتجاهين. ويكون مُمكّنًا افتراضيًا.

عند تمكينه، تُرفَض أي Geometry تخالف قواعد شكل GeoJSON: مثل LineString (أو line ضمن MultiLineString) يحتوي على أقل من نقطتين؛ أو حلقة في Polygon أو MultiPolygon يحتوي على أقل من أربع نقاط، أو تكون نقطتاه الأولى والأخيرة مختلفتين (حلقة غير مغلق)؛ أو MultiLineString أو Polygon أو MultiPolygon فارغ. وتنطبق القواعد نفسها عند قراءة مثل هذا المستند وعند كتابة قيمة ClickHouse من هذا النوع، لذا فإن أي مستند يُكتَب يمكن دائمًا قراءته مرة أخرى.

عند تعطيله، لا تُفرَض قواعد الشكل هذه في أي من الاتجاهين: تُقرأ الأشكال الهندسية المتدهورة كما هي وتُكتب كما هي. ويتيح ذلك لقيم Geometry في ClickHouse التي لا تُعد أشكال GeoJSON هندسية صالحة أن تمر عبر الصيغة ذهابًا وإيابًا، لكن على حساب إنتاج مستندات ليست GeoJSON صالحًا.

يقتصر التحقق على البنية فقط: فهو يفحص عدد النقاط وإغلاق حلقة. ولا يفحص السلامة الهندسية للشكل، لذا تُقبَل أي Geometry صحيحة بنيويًا لكنها متدهورة هندسيًا في أي من الاتجاهين — مثل polygon مساحته صفر، أو حلقة ذاتي التقاطع، أو polygon تقع holes (الحلقات الداخلية) فيه خارج حلقة الخارجي. وبالمثل، لا يُفرَض مطلقًا اتجاه قاعدة اليد اليمنى (الالتفاف) لحلقات polygon.

يوجد فحص واحد مستقل عن هذا الإعداد: الإحداثيات غير المنتهية (NaN, Inf) تُرفَض دائمًا، لأنه لا يمكن تمثيلها كأعداد JSON.

Navigation