В предыдущей статье я писала о создании с помощью различных ИИ довольно простого тестовое iOS приложение Countries («Страны Мира»), которое показывает все страны Мира по регионам (Европа, Азия, Латинская Америка и т.д.) и для каждой страны отображается её название, флаг, численность населения и ВВП. Если вы выбираете какую-то страну, то на карте будет представлена страна с местоположением её столицы..
Я хочу добавить на карту границы страны:

На данный момент я ничего не знаю о том, где взять информацию о границах стран, но это должен быть либо JSON файл, либо JSON информация из интернета, которую можно считать с помощью async / await и декодировать в Swift.
Я хочу обратиться к Grok 3 xAI и попросить его написать для меня Swift код, который бы добавил границы выбранной страны на карту.
Сразу скажу, что я не считаю, что какой-то ИИ может вместо вас написать целое iOS приложение. В лучшем случае ИИ может предложить вам действительно замечательный код с небольшим количеством ошибок (2-5), которые кстати иногда трудно найти даже опытному программисту высшей квалификации ( у меня так было с Claude 3.5 Sonnet, хотя считаю его одним из лучших ИИ для кодинга). Если вы находите эти ошибки самостоятельно или, продолжая спрашивать ИИ, вы действительно получаете высокопрофессиональное iOS приложение, потратив на его разработку существенно меньше времени.
Вот под таким углом мы и будем рассматривать нашу задачу о границах стран на карте, которую будем решать вместе с Grok 3 xAI.
Забегая вперед, скажу, что Grok 3 xAI практически полностью справился со всеми моими запросами, предложив совершенно неожиданное для меня решение со встроенным в MapKit декодером MKGeoJSONDecoder. Тем не менее я попросила Grok 3 создать Codable Модель в Swift для декодирования GeoJSON данных, имеющих не тривиальную JSON структуру, и он с ней превосходно справился. По ходу дела Grok 3 вообще выдал целый «Комплексный обзор о получении GeoJSON данных стран» и предоставил очень четкие «рассуждения», которые вполне можно использовать как отличный обучающий материал.
Так что Grok 3 xAI в этой конкретной задаче зарекомендовал себя серьезным помощником в iOS программировании.
Код iOS приложения Countries с границами стран находится на Github.
Полностью протокол взаимодействия с Grok 3 можно посмотреть здесь. Ниже представлены лишь отдельные фрагменты этого протокола.
Начало


Это «рассуждающий» ИИ, он представляет все свои размышления (здесь они скрыты под меткой «Thoughts», но с ними можно ознакомиться на русском языке здесь) и 15 сайтов, которые он просмотрел.
Окончательный ответ
Чтобы смоделировать границу страны как полигон с использованием геокоординат в SwiftUI с MapKit для iOS 17, вам необходимо получить географические координаты, которые определяют границу страны, и отобразить их в виде наложения полигона на карте. Ниже я объясню, где получить данные о границе страны и как реализовать это в SwiftUI с использованием MapKit, адаптированного для iOS 17.
Где получить данные о границах стран в виде полигона?
Данные о границах стран обычно доступны в виде географических координат в таких форматах, как GeoJSON, которые можно легко интегрировать в SwiftUI приложение. Вот надежные источники для получения этих данных:
- Официальные правительственные источники: многие страны предоставляют географические данные через государственные учреждения. Например, Геологическая служба США (USGS) предлагает наборы данных, которые включают координаты границ. Проверьте портал географических данных интересующей вас страны.
- Платформы открытых данных: OpenStreetMap (OSM) предоставляет бесплатные географические данные из сообщества, включая границы стран. Вы можете извлечь эти данные в формате GeoJSON с помощью таких инструментов, как Overpass API, или загрузив предварительно обработанные наборы данных.
- Файлы GeoJSON из Natural Earth: Natural Earth (naturalearthdata.com) предлагает бесплатные векторные наборы данных, включая набор данных «Admin 0 — Countries» с границами стран. Вы можете загрузить шейп-файлы и преобразовать их в GeoJSON с помощью таких инструментов, как ogr2ogr или онлайн конвертеров, или найти предварительно преобразованные файлы GeoJSON из источников сообщества.
- API: такие сервисы, как Google Maps API или Mapbox API, предоставляют географические данные, включая границы стран, но они часто требуют подписки и имеют ограничения на использование. Для бесплатного и офлайн-решения предпочтительнее файлы GeoJSON.
Рекомендуемый подход: используйте файлы GeoJSON из Natural Earth из-за их надежности, бесплатного доступа и совместимости с MapKit через MKGeoJSONDecoder.
Шаги по моделированию границы страны в SwiftUI с MapKit
- Получите файл GeoJSON:
- Загрузите файл GeoJSON, содержащий границы страны (например, из Natural Earth).
- Включите его в
bundle
вашегоXcode
проекта (например, назовите егоcountries.geojson
).
- Выполните парсинг GeoJSON данных :
- Используйте
MKGeoJSONDecoder
MapKit
для парсинга файла GeoJSON в объекты MapKit. - Определите объект
feature
для выбранной страны и извлеките его геометрию geometry полигона.
- Используйте
- Отобразите полигон на карте:
- Используйте
SwiftUI
MapView
(улучшенное в iOS 17) и структуруMapPolygon
для визуализации границы страны в виде наложения как полигона. - Настройте внешний вид полигона (например, обводку, заливку).
- Используйте
Пример реализации
Вот полный пример, показывающий, как загрузить границу страны из файла GeoJSON
и отобразить ее в SwiftUI
с помощью MapKit
для iOS 17.
Шаг 1. Добавьте файл GeoJSON
- Перетащите файл
countries.geojson
в свой проектXcode
, убедившись, что отмечена опция “Copy items if needed» («Копировать элементы, если необходимо»), и он добавлен в Target вашего приложения.
Шаг 2. Код SwiftUI
import SwiftUI
import MapKit
struct CountryBorderView: View {
// The name of the country to display
let countryName: String
// State to hold the polygons extracted from GeoJSON
@State private var polygons: [MKPolygon] = []
var body: some View {
Map {
// Render each polygon as a MapPolygon overlay
ForEach(polygons, id: \.self) { polygon in
MapPolygon(polygon: polygon)
.stroke(Color.blue, lineWidth: 2) // Border style
.fill(Color.blue.opacity(0.3)) // Fill style
}
}
.frame(height: 400) // Adjust map size as needed
.onAppear {
loadCountryBorder()
}
}
/// Loads and parses the GeoJSON file to extract the country's border polygons
func loadCountryBorder() {
// Locate the GeoJSON file in the app bundle
guard let url =
Bundle.main.url(forResource: "countries", withExtension: "geojson")
else {
print("GeoJSON file not found")
return
}
do {
// Load the file data
let data = try Data(contentsOf: url)
let decoder = MKGeoJSONDecoder()
// Decode into an array of MKGeoJSONObjects
let geoObjects = try decoder.decode(data)
// Process each feature in the GeoJSON
for object in geoObjects {
if let feature = object as? MKGeoJSONFeature,
let propertiesData = feature.properties,
// Decode properties (assuming 'name' is the country identifier)
let properties =
try? JSONSerialization.jsonObject(with: propertiesData) as? [String: Any],
let name = properties["name"] as? String,
name.lowercased() == countryName.lowercased() {
// Extract all geometries (polygons) for the country
for geometry in feature.geometries {
if let geoPolygon = geometry as? MKPolygon {
polygons.append(geoPolygon.geometry)
} else if let geoPolygons = geometry as? MKMultiPolygon {
for polygon in geoPolygons.polygons{
polygons.append(polygon)
}
}
}
}
}
} catch {
print("Error loading GeoJSON: \(error)")
}
}
}
// Example usage in a preview
#Preview {
CountryBorderView(countryName: "Italy")
}
Как это работает:
- Парсинг GeoJSON:
- Функция
loadCountryBorder
загружает файлcountries.geojson
изbundle
приложения. MKGeoJSONDecoder
преобразует данныеGeoJSON
в массив объектовMKGeoJSONObjects
.- Код выполняет итерацию по объектам, подбирая нужную страну по ее свойству
name
(настройте его на основе ключейproperties
GeoJSON, например, name, iso_a3). - Проверяется геометрия
geometry
каждого совпадающего объекта; если этоMKGeoJSONPolygon
, его геометрияgeometry (MKPolygon)
извлекается.
- Функция
- Отображение полигона:
- В
Map View
используется циклForEach
для визуализации каждогоMKPolygon
какMapPolygon
. MapPolygon(polygon:)
напрямую принимаетMKPolygon
, доступный вMapKit
для SwiftUI в iOS 17.- Параметры стиля, такие как обводка и заливка, настраивают внешний вид границы.
- В
- Обработка сложных границ: Некоторые страны (например, Индонезия) имеют границы, состоящие из нескольких полигонов (мультиполигоны). Этот код естественным образом обрабатвает их, добавляя каждый
MKPolygon
в массив полигонов.
Примечания
- Структура GeoJSON: убедитесь, что ваш файл GeoJSON включает свойство name (или настройте код так, чтобы он соответствовал свойствам, таким как iso_a2, iso_a3, или admin). Проверьте структуру файла с помощью текстового редактора или онлайн-просмотрщика GeoJSON.
- Регион карты: чтобы сфокусировать карту на стране, вы можете добавить переменную
@State
для регионаregion
карты Map и установить ее на основе координат полигона (здесь не показано для краткости). - Обработка ошибок: добавьте надежную обработку ошибок для реальных приложений (например, отсутствующие файлы, недействительные данные)
Запускаем приложение и получаем ошибку компиляции:

Убираем строку, вызвавшую ошибку, но все равно получаем новую ошибку уже на этапе выполнения:
Error loading GeoJSON: Error Domain=MKErrorDomain Code=6 «Invalid coordinate position: -84.35279635, +180.00000000» UserInfo={NSDebugDescription=Invalid coordinate position: -84.35279635, +180.00000000}
Проверяем файл countries.geojson
и обнаруживаем, что поля «name
» НЕТ в «properties
«:

Есть поле «ADMIN
» вместо «name
«, и мы исправляем строку кода:
// let name = properties["name"] as? String,
let name = properties["ADMIN"] as? String,
и получаем:

Вместо убранной строки
.fill(Color.blue.opacity(0.3))
… вставим строку
.foregroundStyle(.clear)
… и получаем:

Где взять данные?

Лучшим источником данных GeoJSON по странам является репозиторий GitHub martynafford, который преобразует данные Natural Earth в формат GeoJSON. Этот репозиторий регулярно обновляется, последние данные датированы 24 января 2024 года, что гарантирует вам доступ к самой последней информации о границах стран. Natural Earth — это набор картографических данных, являющийся общественным достоянием, что делает его бесплатным для использования в любом проекте.
Ссылки для загрузки по масштабу
Вы можете загрузить файлы GeoJSON в разных масштабах в зависимости от ваших потребностей в картографии:
- Масштаб 1:110 м (более низкая детализация, меньший размер файла): загрузить здесь
- Масштаб 1:50 м (средняя детализация): загрузить здесь
- Масштаб 1:10 м (наивысшая детализация, большой размер файла): загрузить здесь
Каждая загрузка представляет собой GeoJSON файл с именем ne_[scale]_admin_0_countries.json, который затем можно использовать для картографирования или визуализации.
Почему этот источник?
Интересно, что в то время как Natural Earth предоставляет данные в формате shapefile на своем официальном сайте (Natural Earth Website), репозиторий martynafford преобразует их в GeoJSON, который более удобен для веб-сайтов и может напрямую использоваться во многих картографических приложениях. Это преобразование использует GDAL 3.2.2 от 5 марта 2021 года, что обеспечивает совместимость с современными инструментами.
Комплексный обзор о получении GeoJSON данных стран
В этом обзоре подробно рассматриваются источники получения данных GeoJSON стран, особое внимание уделяется надежности, новизне и удобству использования, в частности, для интеграции в картографические приложения, такие как SwiftUI с MapKit
для iOS 17, как следует из контекста. Анализ включает в себя тщательную оценку потенциальных источников, их версий и процесса доступа к данным, что обеспечивает полное понимание для разработчиков и исследователей
Предыстория и контекст
GeoJSON — это формат для кодирования структур географических данных, идеальный для веб-картографирования благодаря своей структуре на основе JSON, которая легко анализируется современными языками программирования и фреймворками. Для границ стран файлы GeoJSON обычно содержат полигоны, представляющие национальные границы, что необходимо для таких приложений, как те, которые используют MapKit
в iOS 17. Запрос ищет источник таких данных, и, учитывая контекст, мы фокусируемся на общедоступных, надежных наборах данных, в частности, из Natural Earth, известного общедоступного набора картографических данных, доступного в масштабах 1:10 м, 1:50 м и 1:110 м.
Оценка потенциальных источников
Несколько источников были выявлены с помощью веб-поиска и прямого исследования, каждый из которых был оценен на предмет новизны, доступности формата и простоты доступа. В следующей таблице обобщены основные источники:
Источник | Доступный формат | Последняя дата обновления | Замечания |
martynafford/natural-earth-geojson | GeoJSON (zipped) | Январь 24, 2024 | Конвертирует шейп-файлы Natural Earth в GeoJSON, включает все масштабы, активно поддерживается. |
geojson.xyz | GeoJSON (direct) | Версия 3.3.0 | Предоставляет данные Natural Earth, но более старую версию (примерно 2017–2021 гг.), доступны прямые ссылки для скачивания. |
datahub.io/core/geo-countries | GeoJSON | Более 6 лет назад | Создано на основе Natural Earth, устарело, для загрузки требуется премиум-доступ. |
nvkelso/natural-earth-vector | GeoJSON (partial) | Не указан | Отражает данные Natural Earth, включает файлы GeoJSON, но менее четкие обновления. |
Natural Earth Website | Shapefile | Январь 24, 2024 | Официальный источник, последняя версия 4.1, но требует преобразования в GeoJSON (например, с использованием GDAL). |
Подробный анализ главного источника: репозитория martynafford
Репозиторий martynafford/natural-earth-geojson оказался наиболее подходящим из-за его новизны и прямого предоставления файлов GeoJSON. Данные обновлены по состоянию на 24 января 2024 года, что соответствует последнему выпуску Natural Earth (версия 4.1, выведенная из сообщений в блоге и дат обновлений). Процесс преобразования использует GDAL 3.2.2 (от 5 марта 2021 года), что обеспечивает совместимость с современными геопространственными инструментами. Структура репозитория включает:
- Папки для разных масштабов (10 м, 50 м, 110 м).
- Cultural данные содержат ne_[scale]_admin_0_countries.json.
Чтобы получить доступ к данным, пользователи загружают файл для желаемого масштаба (например, ссылка 110 м), извлекают его и используют файл JSON. Этот процесс был подтвержден путем просмотра репозитория, что позволило убедиться в доступности файлов и их корректных названиях.
Сравнение с другими источниками
- geojson.xyz: предлагает прямую загрузку GeoJSON, но его версия (3.3.0) устарела, вероятно, соответствует более раннему выпуску Natural Earth (до 2021 г.), что делает его менее подходящим для приложений, требующих новейших данных. Например, он включает URL-адреса, такие как naturalearth-3.3.0, но не имеет последних обновлений.
- datahub.io/core/geo-countries: предоставляет GeoJSON, но последний раз изменялся более 6 лет назад, что делает его устаревшим для текущих приложений. Ссылка для загрузки (/core/geo-countries/_r/-/data/countries.geojson) требует премиум-доступа, что добавляет сложности.
- Веб-сайт Natural Earth: официальный источник предоставляет шейп-файлы для версии 4.1, обновленной 24 января 2024 г., но требует преобразования в GeoJSON с помощью таких инструментов, как GDAL, что может быть препятствием для нетехнических пользователей. Например, на странице 110m culturals vectors page перечислены файлы без прямого GeoJSON, что требует дополнительных шагов.
Соображения по масштабу
Выбор масштаба (10m, 50m, 110m) зависит от потребностей приложения:
- 110m: более низкая детализация, подходит для глобальных обзоров, меньший размер файла (например, 602,55 КБ для стран admin 0 на geojson.xyz).
- 50m: средняя детализация, сбалансированный для веб-карт, больше 110m, но меньше 10m.
- 10m: самая высокая детализация, самый большой размер файла, идеально подходит для карт с высоким разрешением, но может повлиять на производительность в веб-приложениях.
Репозиторий martynafford предоставляет все масштабы с прямыми ссылками на загрузку, обеспечивающими простоту доступа. Например, файл 10m (ссылка) более подробный, но подходит для приложений, требующих высокой точности.
Дополнительные соображения и альтернативы
Другие источники, такие как OpenStreetMap, рассматривались, но были признаны менее подходящими для границ стран из-за их фокусировки на подробных данных, полученных от сообщества, а не на стандартизированных национальных границах. Организация Объединенных Наций предоставляет данные о границах (каталог FAO), но в основном в других форматах (например, данные 2018 года, а не GeoJSON), и не имеет новизны Natural Earth. Например, запрос GIS Stack Exchange (GIS Stack Exchange) выявил проблемы в поиске специфичного для ООН GeoJSON, подкрепив Natural Earth как предпочтительный источник.
Практическая реализация для iOS 17 MapKit
Учитывая контекст моделирования границ стран в SwiftUI с MapKit
для iOS 17, файлы GeoJSON из репозитория martynafford напрямую совместимы. MKGeoJSONDecoder
MapKit
может парить эти файлы, как показано в примерах реализации, обеспечивая бесшовную интеграцию. Выбор масштаба влияет на производительность: 110 м для быстрой загрузки, 10 м для подробных Views
. Это соответствует вероятным потребностям пользователя в эффективных высококачественных наложениях карт.
Заключение
После оценки нескольких источников рекомендуется martynafford/natural-earth-geojson из-за его новизны (24 января 2024 г.), прямой доступности GEOJSON и соответствия последним данным Natural Earth. Пользователи должны загрузить zip-файл для своего предпочтительного масштаба, извлечь файл JSON и использовать его в своих приложениях. Такой подход обеспечивает доступ к самым актуальным и надежным данным о границах стран для картографирования.
Ключевые ссылки
- Данные Natural Earth в репозитории GeoJSON GitHub
- Официальный сайт Natural Earth для картографических данных
- Доступ к данным GeoJSON через geojson.xyz
- Полигоны стран GEOJSON на datahub.io
- Векторные данные Natural Earth репозиторий GitHub
- Данные ООН о границах стран в каталоге FAO
Чтения удаленного GeoJSON файла

Читай GeoJSON файл в Swift. используя async / await |
Чтобы прочитать удаленные GeoJSON данные в Swift с помощью async / await
, мы воспользуемся современными функциями многопоточности, представленными в Swift 5.5 (iOS 15+). Этот подход упрощает асинхронные операции, такие как декодирование JSON из источника данных, не полагаясь на обработчики завершения. Ниже я адаптирую предыдущую модель и покажу, как декодировать JSON асинхронно, предполагая, что он получается из удаленного URL.
Предварительные условия
- iOS 15+:
async/await
требует Swift 5.5, доступного в iOS 15 или более поздней версии.
Декодирование с удаленного URL
Если JSON размещен в сети (например, URL, подобный тем, что есть у martynafford/natural-earth-geojson):
Функция loadCountryBorder()
func loadCountryBorder() async -> [MKPolygon] {
var polygons: [MKPolygon] = []
let urlString = "https://raw.githubusercontent.com/martynafford/natural-earth-geojson//master/50m/cultural/ne_50m_admin_0_countries.json"
guard let url = URL(string: urlString) else {
print ("Invalid URL")
return []
}
do {
// Load from Remote URL
let (data, _ ) = try await URLSession.shared.data(from: url)
let decoder = MKGeoJSONDecoder()
// Decode into an array of MKGeoJSONObjects
let geoObjects = try decoder.decode(data)
// Process each feature in the GeoJSON
for object in geoObjects {
if let feature = object as? MKGeoJSONFeature,
let propertiesData = feature.properties,
// Decode properties (assuming 'name' is the country identifier)
let properties =
try? JSONSerialization.jsonObject(with: propertiesData) as? [String: Any],
let name = properties["NAME"] as? String,
name.lowercased() == countryName.lowercased() {
// Extract all geometries (polygons) for the country
for geometry in feature.geometry {
if let geoPolygon = geometry as? MKPolygon {
polygons.append(geoPolygon) // Add the MKPolygon
} else if let geoPolygons = geometry as? MKMultiPolygon {
for polygon in geoPolygons.polygons{
polygons.append(polygon) // Add the MKPolygon
}
}
}
}
}
} catch {
print("Error loading GeoJSON: \(error)")
}
return polygons
}
Пример реального URL-адреса: используйте что-то вроде «https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/naturalearthdata/110m/cultural/ne_110m_admin_0_countries.json» (вам нужно будет получить URL-адрес необработанного контента из GitHub).
Интеграция с SwiftUI
Чтобы использовать это в приложении SwiftUI с картой, вы можете обернуть асинхронный вызов в View Model
или непосредственно в View
:
Код CountryBorderView
import SwiftUI
import MapKit
struct CountryBorderView: View {
// The name of the country to display
let countryName: String = "Italy"
// State to hold the polygons extracted from GeoJSON
@State private var polygons: [MKPolygon] = []
var body: some View {
VStack {
if polygons.count > 0 {
Map {
ForEach(0..<polygons.count, id: \.self) { index in
MapPolygon(polygons [index])
.stroke(Color.red, lineWidth: 2)
.foregroundStyle(.clear)
}
}
} else {
Text("Loading...")
}
}
.frame(height: 400) // Adjust map size as needed
.task{
polygons = await loadCountryBorder()
}
}
}
#Preview {
CountryBorderView()
}
Модификатор .task: запускает асинхронное декодирование при появлении View.
Получаем результат:

Ключевые моменты
- Многопоточность: a
sync / await
делает код линейным и более удобным для чтения по сравнению с обработчиками завершенияcompletion handlers
. - Обработка ошибок: синтаксис
try await
легко интегрируется с обработкой ошибок Swift. - Гибкость: выберите метод декодирования (строка, файл, URL) на основе вашего источника данных.
- SwiftUI: модификатор
.task
гарантирует, что асинхронная операция выполняется в контексте SwiftUI, обновляя UI по завершении.
Создание Codable Модели GeoJSON

Дайте мне Codable Model в Swift для данных GeoJSON из «https://raw.githubusercontent.com/martynafford/natural-earth-geojson//master/50m/cultural/ne_50m_admin_0_countries.json«. В свойствах properties укажите только «NAME», «ISO_A2» и «ISO_A3» |
Чтобы создать модель Codable
в Swift для данных GeoJSON из «https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/50m/cultural/ne_50m_admin_0_countries.json«, нам нужно сопоставить структуру файла GeoJSON, ограничив свойства properties
только «NAME», «ISO_A2» и «ISO_A3». Набор данных Natural Earth по этому URL-адресу представляет собой FeatureCollection
, содержащий объекты Feature
, каждый со словарем properties
и geometry
, которая может быть либо «Polygon
«, либо «MultiPolygon
«. Ниже я определю модель, которая эффективно обрабатывает эту структуру.
Обзор структуры GeoJSON
На основе файла по предоставленному URL-адресу:
- Верхний уровень: «
type
«: «FeatureCollection
» с массивомfeatures
. - Содержание Feature: «
type
«: «Feature
«, объектproperties
и объектgeometry
. - Properties: Словарь со многими полями, но мы будем декодировать только «NAME», «ISO_A2» и «ISO_A3».
- Geometry: Может быть «
Polygon
» (координаты как[[[Double]]])
или «MultiPolygon
» (координаты как[[[[Double]]]])
.
Модель будет использовать специальную логику Codable
для динамической обработки ТИПов geometry
.
Swift Codable Модель
import Foundation
// Top-level GeoJSON structure
struct FeatureCollection: Codable {
let type: String
let features: [Feature]
}
// Represents a single feature (e.g., a country)
struct Feature: Codable {
let type: String
let properties: Properties
let geometry: Geometry
}
// Limited properties: only NAME, ISO_A2, and ISO_A3
struct Properties: Codable {
let name: String
let isoA2: String
let isoA3: String
enum CodingKeys: String, CodingKey {
case name = "NAME"
case isoA2 = "ISO_A2"
case isoA3 = "ISO_A3"
}
}
// Geometry with support for Polygon and MultiPolygon
struct Geometry: Codable {
let type: GeometryType
let coordinates: GeometryCoordinates
// Enum for geometry type
enum GeometryType: String, Codable {
case polygon = "Polygon"
case multiPolygon = "MultiPolygon"
}
// Enum for coordinates with associated values
enum GeometryCoordinates {
case polygon(PolygonCoordinates)
case multiPolygon(MultiPolygonCoordinates)
}
// Typealiases for coordinate structures
typealias PolygonCoordinates = [[[Double]]] // [ring[point[lon, lat]]]
typealias MultiPolygonCoordinates = [[[[Double]]]] // [polygon[ring[point[lon, lat]]]]
// Coding keys
enum CodingKeys: String, CodingKey {
case type
case coordinates
}
// Custom decoding
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(GeometryType.self, forKey: .type)
switch type {
case .polygon:
let coords = try container.decode(PolygonCoordinates.self, forKey: .coordinates)
self.type = type
self.coordinates = .polygon(coords)
case .multiPolygon:
let coords = try container.decode(MultiPolygonCoordinates.self, forKey: .coordinates)
self.type = type
self.coordinates = .multiPolygon(coords)
}
}
// Custom encoding (optional, included for completeness)
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
switch coordinates {
case .polygon(let coords):
try container.encode(coords, forKey: .coordinates)
case .multiPolygon(let coords):
try container.encode(coords, forKey: .coordinates)
}
}
}
Эта модель корректно динамически обрабатывает типы «Polygon
» и «MultiPolygon
«, что делает ее надежной для полных наборов данных, таких как Countries.geojson
Natural Earth.
Объяснение
- FeatureCollection:
- Представляет корневой объект «
FeatureCollection
» сtype
(всегда «FeatureCollection
«) и массивомfeatures
.
- Представляет корневой объект «
- Feature:
- Представляет каждую страну или регион с
type
(всегда «Feature
«),properties
иgeometry
.
- Представляет каждую страну или регион с
- Properties:
- Ограничено «NAME», «ISO_A2» и «ISO_A3» по запросу.
- Использует
CodingKeys
для сопоставления ключей JSON (в верхнем регистре) с понятными для Swift именами свойств (camelCase). - Игнорирует все остальные свойства в GeoJSON (например, «POP_EST», «CONTINENT»), поскольку они не указаны.
- Geometry:
- Использует перечисление
enum GeometryType
, чтобы различить «Polygon
» и «MultiPolygon
«. - Использует перечисление
enum GeometryCoordinates
со связанными значениями для динамического хранения координатcoordinates
: PolygonCoordinates = [[[Double]]]
для «Polygon
» (массив колец, где каждое кольцо является массивом[longitude, latitude]
).MultiPolygonCoordinates = [[[[Double]]]]
для «MultiPolygon
» (массив полигонов, каждый с массивом колец)).- Пользовательский
init(from:)
сначала декодируетtype
, затем выбирает соответствующую структуру координатcoordinate
.
- Использует перечисление
Интеграция с MapKit
Чтобы использовать это с MapKit
, используйте расширение extension
для Geometry
с целью преобразования координат coordinates
в [[CLLocationCoordinate2D]]
, которые затем используем в MapPolygon
в Map
SwiftUI View
:
extension Geometry
// More simple
extension Geometry {
func toPolygons() ->[[CLLocationCoordinate2D]] {
var polygons: [[CLLocationCoordinate2D]] = []
switch coordinates {
case .polygon(let rings):
var coordinates2D: [CLLocationCoordinate2D] = []
for point in rings[0] { // First ring only for simplicity
let coordinate =
CLLocationCoordinate2D(latitude: point[1], longitude: point[0])
coordinates2D.append(coordinate)
}
polygons.append(coordinates2D)
case .multiPolygon(let multiRings):
for rings in multiRings {
var coordinates2D: [CLLocationCoordinate2D] = []
for point in rings[0] { // First ring of each polygon
let coordinate =
CLLocationCoordinate2D(latitude: point[1], longitude: point[0])
coordinates2D.append(coordinate)
}
polygons.append(coordinates2D)
}
}
return polygons
}
}
Функция loadCountryBorder()
func loadCountryBorder() async -> [[CLLocationCoordinate2D]] {
var polygons: [[CLLocationCoordinate2D]] = []
let urlString = "https://raw.githubusercontent.com/martynafford/natural-earth-geojson//master/50m/cultural/ne_50m_admin_0_countries.json"
guard let url = URL(string: urlString) else {
print ("Invalid URL")
return []
}
do {
// Load from Remote URL
let (data, _ ) = try await URLSession.shared.data(from: url)
let geoJSON =
try JSONDecoder().decode(FeatureCollection.self, from: data)
for feat in geoJSON.features {
if feat.properties.name == countryName {
polygons = feat.geometry.toPolygons()
}
}
} catch {
print("Error loading GeoJSON: \(error)")
}
return polygons
}
Интеграция с SwiftUI
Будем использовать это в приложении SwiftUI с картой Map
непосредственно в View
:
Код CountryBorderView:
import SwiftUI
import MapKit
struct CountryBorderView1: View {
// The name of the country to display
let countryName: String = "Italy"
// State to hold the polygons extracted from GeoJSON
@State private var polygons: [[CLLocationCoordinate2D]] = []
var body: some View {
VStack {
if polygons.count > 0 {
Map {
ForEach(0..<polygons.count, id: \.self) { index in
MapPolygon(coordinates: polygons [index])
.stroke(Color.red, lineWidth: 2)
.foregroundStyle(.clear)
}
}
} else {
Text("Loading...")
}
}
.frame(height: 400) // Adjust map size as needed
.task{
polygons = await loadCountryBorder()
}
}
}
Получаем практически тот же результат:

Заключение
«Размышляющий» ИИ Grok 3 xAI :
- После оценки нескольких источников данных о границах стран рекомендовал репозиторий martynafford/natural-earth-geojson из-за его новизны (24 января 2024 г.), прямой доступности GeoJSON и соответствия последним данным Natural Earth.
- Предложил код для двух способов декодирования JSON данных:
MKGeoJSONDecoder()
вMapKit
и стандартный JSONDecoder()
- Использовал современную a
sync / await
многопоточную выборку JSON данных из интернета - Интегрировал данные о границах стран в SwiftUI
Map VIew
- Умеет великолепно парить нестандартные JSON данные
- Показал себя эффективным и надежным помощником в iOS программировании
Результаты действительно уже впечатляют. Спасибо за пост, и за прикрепленный протокол общения.