> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> تنسيق إدخال وإخراج لمستندات GeoJSON FeatureCollection: عند الإدخال، صف واحد لكل معلم مع أعمدة id وgeometry وproperties؛ وعند الإخراج، معلم واحد لكل صف.

# GeoJSON

| إدخال | إخراج | اسم مستعار |
| ----- | ----- | ---------- |
| ✔     | ✔     |            |

<div id="description">
  ## الوصف
</div>

تُتبادَل بيانات [GeoJSON](https://geojson.org/) في صورة مستند [`FeatureCollection`](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3) واحد، ويحوّله ClickHouse إلى ثلاثة أعمدة — `id` و`geometry` و`properties` — بحيث تمثل كل مجموعة عنصر `Feature` واحدًا. تؤدي [قراءة](#reading-data) المستند إلى إنتاج صف واحد لكل `Feature`؛ بينما تؤدي [الكتابة](#writing-data) إلى إنتاج `Feature` واحد لكل صف.

<div id="reading-data">
  ## قراءة البيانات
</div>

تنتج قراءة `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` و`LineString` و`MultiLineString` و`Polygon` و`MultiPolygon`. أما نوعا هندسة GeoJSON الآخران، `GeometryCollection` و`MultiPoint`، فلا يمكن تمثيلهما بالنوع `Geometry`؛ وقراءة أحدهما في العمود `geometry` تؤدي افتراضيًا إلى ظهور استثناء، ويمكن تغيير هذا السلوك لإدراج `NULL` بدلًا من ذلك — انظر [التعامل مع أنواع الهندسة غير المدعومة](#unsupported-geometry) أدناه. افتراضيًا، تكون قيمة العمود `geometry` هي `NULL` فقط عندما تكون هندسة المعلم قيمة `null` صريحة في `JSON`؛ ومع `input_format_geojson_unsupported_geometry_handling = 'null'` تكون أيضًا `NULL` عند استخدام نوع هندسة غير مدعوم.

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

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

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

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

```json theme={null}
{
    "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}
        }
    ]
}
```

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

```sql title="Query" theme={null}
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
┌─id─┬─name────────────┬─geo_type───┐
│ 1  │ Tower of London │ Point      │
│ 2  │ River Thames    │ LineString │
│ 3  │ Hyde Park       │ Polygon    │
└────┴─────────────────┴────────────┘
```

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

```sql title="Query" theme={null}
SELECT id, properties.name AS name, variantType(geometry) AS geo_type
FROM file('london.geojson');
```

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

```sql title="Query" theme={null}
SELECT properties.name AS name, geometry, variantType(geometry)
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
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
```

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

```sql title="Query" theme={null}
SELECT properties.name AS name, variantType(geometry), geometry.Point, geometry.LineString, geometry.Polygon
FROM file('london.geojson', GeoJSON);
```

```response title="Response" theme={null}
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 إلى جدول:

```sql title="Query" theme={null}
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);
```

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

```sql title="Query" theme={null}
SELECT name, feature_type, variantType(geometry) AS geo_type
FROM london
ORDER BY id;
```

```response title="Response" theme={null}
┌─name────────────┬─feature_type─┬─geo_type───┐
│ Tower of London │ landmark     │ Point      │
│ River Thames    │ river        │ LineString │
│ Hyde Park       │ park         │ Polygon    │
└─────────────────┴──────────────┴────────────┘
```

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

```sql title="Query" theme={null}
DESCRIBE format(GeoJSON, '{"type":"FeatureCollection","features":[]}');
```

```response title="Response" theme={null}
┌─name───────┬─type─────────────┐
│ id         │ Nullable(String) │
│ geometry   │ Geometry         │
│ properties │ Nullable(JSON)   │
└────────────┴──────────────────┘
```

<div id="unsupported-geometry">
  ### التعامل مع أنواع هندسة غير المدعومة
</div>

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

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

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

<div id="reading-limitations">
  ### القيود
</div>

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

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

<div id="writing-data">
  ## كتابة البيانات
</div>

يؤدي كتابة مجموعة النتائج إلى إنتاج `FeatureCollection` واحدة بتنسيق GeoJSON وفقًا للمواصفة [`FeatureCollection`](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3)، بحيث يقابل كل صف عنصر `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`                       |
| `LineString`      | `LineString`                  |
| `MultiLineString` | `MultiLineString`             |
| `Polygon`         | `Polygon`                     |
| `MultiPolygon`    | `MultiPolygon`                |
| `Ring`            | `Polygon` (حلقة واحدة)        |
| `Geometry`        | نوع المتغير النشط (أو `null`) |

لا يُعَد `Ring` نوعًا هندسيًا في GeoJSON — إذ إن [الحلقة الخطية](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6) هي أحد مكونات `Polygon` — لذلك تُكتَب قيمة `Ring` على هيئة `Polygon` ذي حلقة واحدة.

<div id="writing-examples">
  ### أمثلة
</div>

بمتابعة استخدام جدول `london` [الذي أُنشئ أعلاه](#reading-data)، فإن تصدير أعمدة السمات العادية يحوّل كل عمود، باستثناء `id` و`geometry`، إلى خاصية:

```sql title="Query" theme={null}
SELECT id, geometry, name, feature_type
FROM london
ORDER BY id
FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"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` هي الأعمدة التي جرى استنتاجها للملف):

```sql title="Query" theme={null}
SELECT * FROM file('london.geojson', GeoJSON) FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"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`):

```sql title="Query" theme={null}
SELECT 42 AS id, (-0.1276, 51.5072)::Point AS geometry FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"geometry":{"type":"Point","coordinates":[-0.1276,51.5072]},"properties":{}}]}
```

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

```sql title="Query" theme={null}
SELECT [(0., 0.), (10., 0.), (10., 10.), (0., 0.)]::Ring AS geometry FORMAT GeoJSON;
```

```response title="Response" theme={null}
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,0]]]},"properties":{}}]}
```

<div id="writing-to-a-file">
  ### الكتابة إلى ملف
</div>

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

```sql title="Query" theme={null}
SELECT id, geometry, properties
FROM london
ORDER BY id
INTO OUTFILE 'london_export.geojson'
FORMAT GeoJSON;
```

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

```sql title="Query" theme={null}
INSERT INTO FUNCTION file('london_export.geojson', GeoJSON)
SELECT id, geometry, properties FROM london;
```

<div id="reading-limitations">
  ### القيود
</div>

<Note>
  لا تتضمن الأنواع الجغرافية في ClickHouse أي نظام إسناد للإحداثيات، لذا يفترض الإخراج أن الإحداثيات هي أصلًا إحداثيات WGS84 بخط الطول/خط العرض وبالترتيب `[longitude, latitude]`، كما يقتضي [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946#section-4). ولا تُجرى أي إعادة إسقاط أو تبديل للمحاور، لذلك فإن الإحداثيات المسقطة — أو البيانات المخزنة بالصيغة `(latitude, longitude)` — تنتج GeoJSON صحيحًا من حيث البنية لكنه غير مطابق للمواصفة.
</Note>

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

* لا يمكن إعادة إنتاج المعلومات التي تُفقَد عند القراءة — مثل ارتفاع الموضع، و`bbox`، والأعضاء الخارجية، والتمييز في `id` بين كونه سلسلة نصية أو رقمًا — راجع [قيود القراءة](#reading-limitations).
* تُكتَب الإحداثيات من قيم `Float64` باستخدام أقصر تمثيل يضمن استعادتها كما هي.
* يُخرَج كائن `properties` المأخوذ مباشرةً من عمود `JSON` وفق ترتيب المفاتيح القياسي لنوع `JSON`، وقد يختلف ذلك عن الإدخال.

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

<div id="geometry-validation">
  ## التحقق من صحة الهندسة
</div>

يتحكم الإعداد `format_geojson_validate_geometry` في ما إذا كانت الصيغة تفرض قواعد شكل Geometry وفقًا لـ [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1)، في كلا الاتجاهين. ويكون مُمكّنًا افتراضيًا.

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

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

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

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