Files
GIS/OOPT/Преобразование в СК - Поворотные точки (разные варианты) - MAPINFO.ipynb

102 KiB
Raw Permalink Blame History

0. CSV координаты в шейпфайл

In [6]:
import geopandas as gpd
import pandas as pd
import folium
import os
In [ ]:
# Считываем проекцию в формате WKT и сохраняем ее в переменной
from pyproj import CRSa
from shapely.geometry import LineString

csv = '4_Маршруты_shapefiles (от Волковой)\\Исходники\\Орнитологический маршрут.csv'

df = pd.read_csv(csv, sep=';', decimal=',')

file_name = os.path.splitext(os.path.basename(csv))[0]

# Создаем геометрию Point из координат
geometry = gpd.points_from_xy(df['X'], df['Y'])

# Создаем GeoDataFrame Gauss Kruger Zone 7= 20007
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs=32637)
gdf.to_file(file_name + '.shp')
In [ ]:
gdf.explore(tiles='Esri.WorldImagery')

МСК координаты в шейп (далее уже в полигон)

In [1]:
# Считываем проекцию в формате WKT и сохраняем ее в переменной
from pyproj import CRS
from shapely.geometry import LineString

csv = 'Кладбище поселка Прикубанский.csv'

df = pd.read_csv(csv, sep=';', decimal=',')

file_name = os.path.splitext(os.path.basename(csv))[0]

# Создаем геометрию Point из координат
geometry = gpd.points_from_xy(df['Y'], df['X'])

# Создаем GeoDataFrame Gauss Kruger Zone 7= 20007
from pyproj import CRS
with open(r'F:\PROJECT-Colormap\Преобразования географических проекций (СК, ГСК-2011, МСК)\МСК23 проекции Краснодар\MCK-23_Zone_2N.prj', 'r') as f:
    msk23_2 = f.read()

gdf = gpd.GeoDataFrame(df, geometry=geometry, crs=msk23_2)
gdf.to_file(file_name + '.shp')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 7
      3 from shapely.geometry import LineString
      5 csv = 'Кладбище поселка Прикубанский.csv'
----> 7 df = pd.read_csv(csv, sep=';', decimal=',')
      9 file_name = os.path.splitext(os.path.basename(csv))[0]
     11 # Создаем геометрию Point из координат

NameError: name 'pd' is not defined
In [14]:
import geopandas as gpd
import pandas as pd
from shapely.geometry import Polygon

# Читаем CSV
df = pd.read_csv('Кладбище поселка Прикубанский.csv', delimiter=';', decimal=',')

# Сортируем по столбцу с порядком точек (N)
df_sorted = df.sort_values(by='N')

# Создаём список координат (X, Y)
coords = list(zip(df_sorted['Y'], df_sorted['X']))

# Убедимся, что полигон замкнут (повторяем первую точку в конце)
if coords[0] != coords[-1]:
    coords.append(coords[0])

# Создаём полигон
polygon = Polygon(coords)

# Создаём GeoDataFrame с одним объектом
gdf = gpd.GeoDataFrame(index=[0], geometry=[polygon])

# Создаем GeoDataFrame Gauss Kruger Zone 7= 20007
from pyproj import CRS
with open(r'F:\PROJECT-Colormap\Преобразования географических проекций (СК, ГСК-2011, МСК)\МСК23 проекции Краснодар\MCK-23_Zone_2N.prj', 'r') as f:
    msk23_2 = f.read()
gdf.set_crs(msk23_2, inplace=True)

# Сохраняем в shapefile
gdf.to_file('polygon.shp')

print("Shapefile с полигоном успешно сохранён: polygon.shp")
Shapefile с полигоном успешно сохранён: polygon.shp

1. Преобразование файлов (shapefile, DXF) в различные координатные системы (в архивы и по папкам) - ОСНОВНОЕ ПО ООПТ

In [2]:
pip install folium
Collecting folium
  Downloading folium-0.20.0-py2.py3-none-any.whl.metadata (4.2 kB)
Collecting branca>=0.6.0 (from folium)
  Downloading branca-0.8.2-py3-none-any.whl.metadata (1.7 kB)
Requirement already satisfied: jinja2>=2.9 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from folium) (3.1.6)
Requirement already satisfied: numpy in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from folium) (2.4.2)
Requirement already satisfied: requests in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from folium) (2.32.5)
Collecting xyzservices (from folium)
  Downloading xyzservices-2025.11.0-py3-none-any.whl.metadata (4.3 kB)
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from jinja2>=2.9->folium) (3.0.3)
Requirement already satisfied: charset_normalizer<4,>=2 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from requests->folium) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from requests->folium) (3.11)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from requests->folium) (2.6.3)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from requests->folium) (2026.1.4)
Downloading folium-0.20.0-py2.py3-none-any.whl (113 kB)
Downloading branca-0.8.2-py3-none-any.whl (26 kB)
Downloading xyzservices-2025.11.0-py3-none-any.whl (93 kB)
Installing collected packages: xyzservices, branca, folium

   ---------------------------------------- 3/3 [folium]

Successfully installed branca-0.8.2 folium-0.20.0 xyzservices-2025.11.0
Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: C:\Users\Lenovo\AppData\Local\Python\pythoncore-3.14-64\python.exe -m pip install --upgrade pip
In [1]:
import geopandas as gpd
import pandas as pd
import folium
import os
In [2]:
# Функция создания списка шейпфайлов для обработки
def shp_lst(folder):
    lst = []
    for file in os.listdir(folder):
        if file.endswith(".shp"):
            lst.append(os.path.join(folder, file))
    print(lst[:2], '\n', f'Всего: {len(lst)} файл(-а, -ов)')
    return lst
In [3]:
# Считываем проекцию в формате WKT и сохраняем ее в переменной
from pyproj import CRS
with open(r'c:\ГИС-проекты\2025 - ООПТ\ООПТ из Программы\MSK23_prj\Russia_Cadastre_23_1.prj', 'r') as f:
    #wkt = f.read()
    msk23_1 = f.read()
# crs = CRS.from_wkt(wkt)

with open(r'c:\ГИС-проекты\2025 - ООПТ\ООПТ из Программы\MSK23_prj\Russia_Cadastre_23_2.prj', 'r') as f:
    msk23_2 = f.read()

# msk23_1 = '+proj=tmerc +lat_0=0 +lon_0=37.98333333333 +k=1 +x_0=1300000 +y_0=-4511057.628 +ellps=krass +towgs84=23.57,-140.95,-78.9,0,0,0,0.22 +units=m +no_defs'
# msk23_2 = '+proj=tmerc +lat_0=0 +lon_0=40.98333333333 +k=1 +x_0=2300000 +y_0=-4511057.628 +ellps=krass +towgs84=23.57,-140.95,-78.9,0,0,0,0.22 +units=m +no_defs'

# Границы районов края со столбцом зон МСК23
gdf_boundary = gpd.read_file(r'c:\ГИС-проекты\2025 - ООПТ\ООПТ из Программы\Слои КК\Borders-adm-MSK-Zone.shp')
# Преобразуем в WGS84
gdf_boundary = gdf_boundary.to_crs(4326)
gdf_boundary.crs
Out[3]:
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich
In [4]:
# Создадим два фрейма с зонам МСК23 в проекции 4326 (для простоты)
msk23_1_boundary = gdf_boundary[gdf_boundary['MSK23_Zone']==1].union_all()
msk23_1_boundary = gpd.GeoDataFrame({'geometry': [msk23_1_boundary]}, crs=gdf_boundary.crs)

msk23_2_boundary = gdf_boundary[gdf_boundary['MSK23_Zone']==2].union_all()
msk23_2_boundary = gpd.GeoDataFrame({'geometry': [msk23_2_boundary]}, crs=gdf_boundary.crs)
In [5]:
# Словарь для создания папки для района
rayon_dic = {
            'esk': 'Ейский район',
            'kvk': 'Кавказский район',
            'kln': 'Калининский район',
            'knv': 'Каневской район',
            'krl': 'Крыловский район',
            'krm': 'Крымский район',
            'ksh': 'Кущевский район',
            'mst': 'Мостовской район',
            'nvk': 'Новокубанский район',
            'pvl': 'Павловский район',
            'tbl': 'Тбилисский район',
            'tps': 'Туапсинский район',
            'usp': 'Успенский район',
            'ulb': 'Усть-Лабинский район',
            'anp': 'Анапский район',
            'brh': 'Брюховецкий район',
            'glj': 'Геленджикский район',
            'nvp': 'Новопокровский район',
            'nvr': 'Новороссийск',
            'otr': 'Отрадненский район',
            'sch': 'Сочи',
            'svr': 'Северский район',
            'tmr': 'Темрюкский район',
            'shr': 'Щербиновский район'
} 
In [6]:
folder = r'C:\ГИС-проекты\2025 - ООПТ\ООПТ из Программы\Input_processing'
shp_lst = shp_lst(folder)
['C:\\ГИС-проекты\\2025 - ООПТ\\ООПТ из Программы\\Input_processing\\nvr-vasilevskie_osypi_pp_orz-new.shp'] 
 Всего: 1 файл(-а, -ов)
In [29]:
pip install ezdxf
Collecting ezdxf
  Downloading ezdxf-1.4.3-py3-none-any.whl.metadata (9.9 kB)
Collecting pyparsing>=2.0.1 (from ezdxf)
  Downloading pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)
Requirement already satisfied: typing_extensions>=4.6.0 in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from ezdxf) (4.15.0)
Requirement already satisfied: numpy in c:\users\lenovo\appdata\local\python\pythoncore-3.14-64\lib\site-packages (from ezdxf) (2.4.2)
Collecting fonttools (from ezdxf)
  Downloading fonttools-4.61.1-cp314-cp314-win_amd64.whl.metadata (116 kB)
Downloading ezdxf-1.4.3-py3-none-any.whl (1.3 MB)
   ---------------------------------------- 0.0/1.3 MB ? eta -:--:--
   ---------------------------------------- 0.0/1.3 MB ? eta -:--:--
   ---------------------------------------- 0.0/1.3 MB ? eta -:--:--
   ------- -------------------------------- 0.3/1.3 MB ? eta -:--:--
   ------- -------------------------------- 0.3/1.3 MB ? eta -:--:--
   --------------- ------------------------ 0.5/1.3 MB 663.7 kB/s eta 0:00:02
   --------------- ------------------------ 0.5/1.3 MB 663.7 kB/s eta 0:00:02
   ----------------------- ---------------- 0.8/1.3 MB 626.9 kB/s eta 0:00:01
   ----------------------- ---------------- 0.8/1.3 MB 626.9 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ------------------------------- -------- 1.0/1.3 MB 588.7 kB/s eta 0:00:01
   ---------------------------------------- 1.3/1.3 MB 371.6 kB/s  0:00:03
Downloading pyparsing-3.3.2-py3-none-any.whl (122 kB)
Downloading fonttools-4.61.1-cp314-cp314-win_amd64.whl (2.3 MB)
   ---------------------------------------- 0.0/2.3 MB ? eta -:--:--
   ---------------------------------------- 0.0/2.3 MB ? eta -:--:--
   ---------------------------------------- 0.0/2.3 MB ? eta -:--:--
   ---- ----------------------------------- 0.3/2.3 MB ? eta -:--:--
   ---- ----------------------------------- 0.3/2.3 MB ? eta -:--:--
   ---- ----------------------------------- 0.3/2.3 MB ? eta -:--:--
   --------- ------------------------------ 0.5/2.3 MB 389.7 kB/s eta 0:00:05
   --------- ------------------------------ 0.5/2.3 MB 389.7 kB/s eta 0:00:05
   --------- ------------------------------ 0.5/2.3 MB 389.7 kB/s eta 0:00:05
   ------------- -------------------------- 0.8/2.3 MB 463.1 kB/s eta 0:00:04
   ------------- -------------------------- 0.8/2.3 MB 463.1 kB/s eta 0:00:04
   ------------- -------------------------- 0.8/2.3 MB 463.1 kB/s eta 0:00:04
   ------------------ --------------------- 1.0/2.3 MB 432.6 kB/s eta 0:00:03
   ------------------ --------------------- 1.0/2.3 MB 432.6 kB/s eta 0:00:03
   ------------------ --------------------- 1.0/2.3 MB 432.6 kB/s eta 0:00:03
   ---------------------- ----------------- 1.3/2.3 MB 436.2 kB/s eta 0:00:03
   ---------------------- ----------------- 1.3/2.3 MB 436.2 kB/s eta 0:00:03
   ---------------------- ----------------- 1.3/2.3 MB 436.2 kB/s eta 0:00:03
   --------------------------- ------------ 1.6/2.3 MB 431.0 kB/s eta 0:00:02
   --------------------------- ------------ 1.6/2.3 MB 431.0 kB/s eta 0:00:02
   ------------------------------- -------- 1.8/2.3 MB 447.5 kB/s eta 0:00:02
   ------------------------------- -------- 1.8/2.3 MB 447.5 kB/s eta 0:00:02
   ------------------------------- -------- 1.8/2.3 MB 447.5 kB/s eta 0:00:02
   ------------------------------- -------- 1.8/2.3 MB 447.5 kB/s eta 0:00:02
   ------------------------------------ --- 2.1/2.3 MB 421.3 kB/s eta 0:00:01
   ------------------------------------ --- 2.1/2.3 MB 421.3 kB/s eta 0:00:01
   ------------------------------------ --- 2.1/2.3 MB 421.3 kB/s eta 0:00:01
   ---------------------------------------- 2.3/2.3 MB 421.1 kB/s  0:00:05
Installing collected packages: pyparsing, fonttools, ezdxf

   ------------- -------------------------- 1/3 [fonttools]
   ------------- -------------------------- 1/3 [fonttools]
   ------------- -------------------------- 1/3 [fonttools]
   ------------- -------------------------- 1/3 [fonttools]
   ------------- -------------------------- 1/3 [fonttools]
   ------------- -------------------------- 1/3 [fonttools]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   -------------------------- ------------- 2/3 [ezdxf]
   ---------------------------------------- 3/3 [ezdxf]

Successfully installed ezdxf-1.4.3 fonttools-4.61.1 pyparsing-3.3.2
Note: you may need to restart the kernel to use updated packages.
  WARNING: The scripts fonttools.exe, pyftmerge.exe, pyftsubset.exe and ttx.exe are installed in 'C:\Users\Lenovo\AppData\Local\Python\pythoncore-3.14-64\Scripts' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The script ezdxf.exe is installed in 'C:\Users\Lenovo\AppData\Local\Python\pythoncore-3.14-64\Scripts' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: C:\Users\Lenovo\AppData\Local\Python\pythoncore-3.14-64\python.exe -m pip install --upgrade pip
In [9]:
### Сохраняет файлы в папку с именем района, которая создается автоматически по трем первым буквам шейп-файла
import ezdxf
import os
import zipfile
import fiona

# Функция для создания архива шейпа в той же папке
# filepath - путь к файлу, outfolder - папка для сохранения
def zip_shp(filepath, outfolder):
    filename = os.path.splitext(os.path.basename(filepath))[0]
    folder = os.path.dirname(filepath)
    with zipfile.ZipFile(outfolder + "//" + f'{filename}.zip', 'w') as zip_file:
        # Ищем файлы с одинаковым именем, но с разным расширением
        for file in os.listdir(folder):
            if os.path.splitext(file)[0] == filename:
                # Добавляем файл в архив
                zip_file.write(os.path.join(folder, file), file)  
                # Удаляет файл
                os.remove(os.path.join(folder, file))
In [5]:
pip install GDAL-3.9.1-cp314-cp314-win_amd64.whl
pip install fiona-1.10.1-cp314-cp314-win_amd64.whl
Processing c:\гис-проекты\fiona-1.10.1-cp314-cp314-win_amd64.whl
Note: you may need to restart the kernel to use updated packages.
WARNING: Requirement 'fiona-1.10.1-cp314-cp314-win_amd64.whl' looks like a filename, but the file does not exist
ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: 'C:\\ГИС-проекты\\fiona-1.10.1-cp314-cp314-win_amd64.whl'


[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: C:\Users\Lenovo\AppData\Local\Python\pythoncore-3.14-64\python.exe -m pip install --upgrade pip
In [11]:
# Временная папка
temp = 'temp'
os.makedirs(temp, exist_ok=True)
                
# Преобразуем все файлы в проекцию WGS84 и сохраняем в формате shape и DVG
for shp in shp_lst:
    gdf = gpd.read_file(shp)
    # Создать папку для района
    outfolder = rayon_dic[os.path.basename(shp)[:3]] # берет первый три буквы названия файла и достает из словаря полное название района
    os.makedirs(outfolder, exist_ok=True)
    
    ### WGS84
    gdf = gdf.to_crs(4326)
    filepath = os.path.join(temp, os.path.splitext(os.path.basename(shp))[0]) + '_WGS84' + '.shp'
    gdf.to_file(filename=filepath, driver='ESRI Shapefile')
    zip_shp(filepath, outfolder)
    # DXF
    filepath = os.path.join(outfolder, os.path.splitext(os.path.basename(shp))[0]) + '_WGS84' + '.dxf'
    #gdf.geometry.to_file(filepath, driver="DXF")
    gdf.geometry.to_file(filepath, driver="DXF", engine='fiona')
    
    # Раздел с МСК23 (распределение по зонам)
    # Для определения делаем центроид полигона, если он внутри зоны МСК1, то это зона 1, если нет, то зона 2 (предварительно пришлось преобразовать WGS84 в Меркатор - не работало)
    # if gdf.to_crs(3857).geometry.centroid.intersects(msk23_1_boundary).any():
    if gdf.geometry.centroid.intersects(msk23_1_boundary).any():
        gdf = gdf.to_crs(msk23_1)
        filepath = os.path.join(temp, os.path.splitext(os.path.basename(shp))[0]) + '_MSK23_1' + '.shp'
        gdf.to_file(filename=filepath, driver='ESRI Shapefile')
        zip_shp(filepath, outfolder)
        # DXF
        filepath = os.path.join(outfolder, os.path.splitext(os.path.basename(shp))[0]) + '_MSK23_1' + '.dxf'
        gdf.geometry.to_file(filepath, driver="DXF")
    else:
        gdf = gdf.to_crs(msk23_2)
        filepath = os.path.join(temp, os.path.splitext(os.path.basename(shp))[0]) + '_MSK23_2' + '.shp'
        gdf.to_file(filename=filepath, driver='ESRI Shapefile')
        zip_shp(filepath, outfolder)
        # DXF
        filepath = os.path.join(outfolder, os.path.splitext(os.path.basename(shp))[0]) + '_MSK23_2' + '.dxf'
        gdf.geometry.to_file(filepath, driver="DXF")


                       
#     with tempfile.TemporaryDirectory() as temp_dir:
#         temp_dir = Path(temp_dir)
#         localFile = 'myshapefile'

#         gdf.to_file(filename=temp_dir, driver='ESRI Shapefile')

#         archiveFile = shutil.make_archive(localFile, 'zip', temp_dir)
#         shutil.rmtree(temp_dir)
       
#     # Преобразуем данные в формат DWG-DXF
#     filepath = os.path.join(outfolder, os.path.splitext(os.path.basename(shp))[0]) + '.dxf'
    
#     # Create a new DXF document
#     doc = ezdxf.new(dxfversion='R2010')
#     # Add a new layer to the DXF document
#     msp = doc.modelspace()

#     # Iterate through the GeoDataFrame and add geometries to the DXF
#     for _, row in gdf.iterrows():
#         if row.geometry.type == 'Point':
#             msp.add_point((row.geometry.x, row.geometry.y))
#         elif row.geometry.type in ['LineString', 'MultiLineString']:
#             for line in row.geometry:
#                 msp.add_lwpolyline(line.coords)
#         elif row.geometry.type in ['Polygon', 'MultiPolygon']:
#             for poly in row.geometry:
#                 coords = list(poly.exterior.coords)
#                 coords.append(coords[0])  # добавляем последнюю точку координат к началу списка координат
#                 msp.add_lwpolyline(coords)

#     # Save the DXF file
#     doc.saveas(filepath)
C:\Users\Lenovo\AppData\Local\Temp\ipykernel_14212\3067829712.py:25: UserWarning: Geometry is in a geographic CRS. Results from 'centroid' are likely incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a projected CRS before this operation.

  if gdf.geometry.centroid.intersects(msk23_1_boundary).any():
Преобразование файлов в DXF
In [87]:
for shp in shp_lst:
    gdf = gpd.read_file(shp)
    # Создать папку для района
    outfolder = rayon_dic[os.path.basename(shp)[:3]] # берет первый три буквы названия файла и достает из словаря полное название района
    os.makedirs(outfolder, exist_ok=True)
    
    # DXF
    filepath = os.path.join(outfolder, os.path.splitext(os.path.basename(shp))[0]) + '.dxf'
    gdf.geometry.to_file(filepath, driver="DXF")

!!!!! ДАЛЕЕ НУЖНО РАЗБИРАТЬСЯ https://mapscaping.com/shapefile-to-dxf/

In [90]:
import geopandas as gpd
import ezdxf

# Load the Shapefile
gdf = gpd.read_file('input.shp')

# Create a new DXF document
doc = ezdxf.new(dxfversion='R2010')

# Add a new layer to the DXF document
msp = doc.modelspace()

# Iterate through the GeoDataFrame and add geometries to the DXF
for _, row in gdf.iterrows():
    if row.geometry.type == 'Point':
        msp.add_point((row.geometry.x, row.geometry.y))
    elif row.geometry.type in ['LineString', 'MultiLineString']:
        for line in row.geometry:
            msp.add_lwpolyline(line.coords)
    elif row.geometry.type in ['Polygon', 'MultiPolygon']:
        for poly in row.geometry:
            msp.add_lwpolyline(poly.exterior.coords, is_closed=True)

# Save the DXF file
doc.saveas('output.dxf')
---------------------------------------------------------------------------
DataSourceError                           Traceback (most recent call last)
Cell In[90], line 5
      2 import ezdxf
      4 # Load the Shapefile
----> 5 gdf = gpd.read_file('input.shp')
      7 # Create a new DXF document
      8 doc = ezdxf.new(dxfversion='R2010')

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\geopandas\io\file.py:299, in _read_file(filename, bbox, mask, columns, rows, engine, **kwargs)
    296             from_bytes = True
    298 if engine == "pyogrio":
--> 299     return _read_file_pyogrio(
    300         filename, bbox=bbox, mask=mask, columns=columns, rows=rows, **kwargs
    301     )
    303 elif engine == "fiona":
    304     if pd.api.types.is_file_like(filename):

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\geopandas\io\file.py:549, in _read_file_pyogrio(path_or_bytes, bbox, mask, rows, **kwargs)
    540     warnings.warn(
    541         "The 'include_fields' and 'ignore_fields' keywords are deprecated, and "
    542         "will be removed in a future release. You can use the 'columns' keyword "
   (...)
    545         stacklevel=3,
    546     )
    547     kwargs["columns"] = kwargs.pop("include_fields")
--> 549 return pyogrio.read_dataframe(path_or_bytes, bbox=bbox, **kwargs)

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pyogrio\geopandas.py:265, in read_dataframe(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, fid_as_index, use_arrow, on_invalid, arrow_to_pandas_kwargs, **kwargs)
    260 if not use_arrow:
    261     # For arrow, datetimes are read as is.
    262     # For numpy IO, datetimes are read as string values to preserve timezone info
    263     # as numpy does not directly support timezones.
    264     kwargs["datetime_as_string"] = True
--> 265 result = read_func(
    266     path_or_buffer,
    267     layer=layer,
    268     encoding=encoding,
    269     columns=columns,
    270     read_geometry=read_geometry,
    271     force_2d=gdal_force_2d,
    272     skip_features=skip_features,
    273     max_features=max_features,
    274     where=where,
    275     bbox=bbox,
    276     mask=mask,
    277     fids=fids,
    278     sql=sql,
    279     sql_dialect=sql_dialect,
    280     return_fids=fid_as_index,
    281     **kwargs,
    282 )
    284 if use_arrow:
    285     meta, table = result

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pyogrio\raw.py:198, in read(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, return_fids, datetime_as_string, **kwargs)
     59 """Read OGR data source into numpy arrays.
     60 
     61 IMPORTANT: non-linear geometry types (e.g., MultiSurface) are converted
   (...)
    194 
    195 """
    196 dataset_kwargs = _preprocess_options_key_value(kwargs) if kwargs else {}
--> 198 return ogr_read(
    199     get_vsi_path_or_buffer(path_or_buffer),
    200     layer=layer,
    201     encoding=encoding,
    202     columns=columns,
    203     read_geometry=read_geometry,
    204     force_2d=force_2d,
    205     skip_features=skip_features,
    206     max_features=max_features or 0,
    207     where=where,
    208     bbox=bbox,
    209     mask=_mask_to_wkb(mask),
    210     fids=fids,
    211     sql=sql,
    212     sql_dialect=sql_dialect,
    213     return_fids=return_fids,
    214     dataset_kwargs=dataset_kwargs,
    215     datetime_as_string=datetime_as_string,
    216 )

File pyogrio\\_io.pyx:1240, in pyogrio._io.ogr_read()

File pyogrio\\_io.pyx:220, in pyogrio._io.ogr_open()

DataSourceError: input.shp: No such file or directory
In [ ]:
# Для проекции в МСК 
# В начале определяем зону по файлу с районами, далее преобразуем в эту зону
# НЕ ДОДЕЛАН!!!!

for shp in shp_lst[:3]:
    gdf = gpd.read_file(shp).union_all
    centroid = gdf.centroid
    centroid_gdf = gpd.GeoDataFrame(geometry=gpd.points_from_xy(centroid.x, centroid.y)).set_crs(3857)
    gdf_joined = gpd.sjoin(
    
    
    gdf = gdf.to_crs(msk23_1)
    
    filepath = os.path.join(outfolder, os.path.basename(shp))
    gdf.to_file(filepath)

    
# Преобразуем данные в формат DWG-DXF
import osgeo import ogr
driver = ogr.GetDriverByName('DXF')
ds = driver.CreateCopy('output.dxf', gdf)
ds = None

import pydxf
dxf = pydxf.DxfFile('input.dxf')
# Преобразуем DXF в DWG
dwg = pydxf.DwfFile('output.dwg')
dwg.write(dxf)
In [ ]:
import pyproj
x, y = gdf.centroid.x, gdf.centroid.y
lon, lat = pyproj.transform(pyproj.Proj('epsg:3857'), pyproj.Proj('epsg:4326'), x, y)
In [ ]:
# Определите зону MSK
if central_meridian >= 45 and central_meridian < 48:
    print("Проекция: MSK-23.1")
elif central_meridian >= 48 and central_meridian <= 51:
    print("Проекция: MSK-23.2")
else:
    print("Проекция: MSK, но зона не определена")
In [ ]:
centroid = gdf.centroid
gdf_joined = gpd.sjoin(centroid, gdf, how='inner', op='within').explore()
In [1]:
centroid_gdf = gpd.GeoDataFrame(geometry=gpd.points_from_xy(centroid.x, centroid.y)).set_crs(msk23_1)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 1
----> 1 centroid_gdf = gpd.GeoDataFrame(geometry=gpd.points_from_xy(centroid.x, centroid.y)).set_crs(msk23_1)

NameError: name 'gpd' is not defined

2. Создание таблицы с координатами (поворотные точки) ТОЛЬКО МСК23

In [9]:
shp = '11111\\sch-aibga-grushevn_pp_orz-new_MSK23_2.shp'
# Извлекаем имя файла
shp_name = os.path.splitext(os.path.basename(shp))[0]
gdf = gpd.read_file(shp)

# Извлекаем координаты вертексов полигона (формат X, Y в текущей координатной системе)
df = gdf.get_coordinates()
# Добавляем индекс к таблице
df.reset_index(inplace = True, drop = True)
# Первая строка с 1
df.index += 1
df = df.reset_index()
# Округляем
df = round(df)
# Переименовываем столбцы
df.columns = ['N', 'X_MSK23', 'Y_MSK23']
# Добавляет первую строку в конец таблицы
df = df.take(list(range(len(df) - 1)) + [0])
df.to_excel(shp_name + '.xlsx', index=False)
---------------------------------------------------------------------------
DataSourceError                           Traceback (most recent call last)
Cell In[9], line 4
      2 # Извлекаем имя файла
      3 shp_name = os.path.splitext(os.path.basename(shp))[0]
----> 4 gdf = gpd.read_file(shp)
      6 # Извлекаем координаты вертексов полигона (формат X, Y в текущей координатной системе)
      7 df = gdf.get_coordinates()

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\geopandas\io\file.py:299, in _read_file(filename, bbox, mask, columns, rows, engine, **kwargs)
    296             from_bytes = True
    298 if engine == "pyogrio":
--> 299     return _read_file_pyogrio(
    300         filename, bbox=bbox, mask=mask, columns=columns, rows=rows, **kwargs
    301     )
    303 elif engine == "fiona":
    304     if pd.api.types.is_file_like(filename):

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\geopandas\io\file.py:549, in _read_file_pyogrio(path_or_bytes, bbox, mask, rows, **kwargs)
    540     warnings.warn(
    541         "The 'include_fields' and 'ignore_fields' keywords are deprecated, and "
    542         "will be removed in a future release. You can use the 'columns' keyword "
   (...)
    545         stacklevel=3,
    546     )
    547     kwargs["columns"] = kwargs.pop("include_fields")
--> 549 return pyogrio.read_dataframe(path_or_bytes, bbox=bbox, **kwargs)

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pyogrio\geopandas.py:265, in read_dataframe(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, fid_as_index, use_arrow, on_invalid, arrow_to_pandas_kwargs, **kwargs)
    260 if not use_arrow:
    261     # For arrow, datetimes are read as is.
    262     # For numpy IO, datetimes are read as string values to preserve timezone info
    263     # as numpy does not directly support timezones.
    264     kwargs["datetime_as_string"] = True
--> 265 result = read_func(
    266     path_or_buffer,
    267     layer=layer,
    268     encoding=encoding,
    269     columns=columns,
    270     read_geometry=read_geometry,
    271     force_2d=gdal_force_2d,
    272     skip_features=skip_features,
    273     max_features=max_features,
    274     where=where,
    275     bbox=bbox,
    276     mask=mask,
    277     fids=fids,
    278     sql=sql,
    279     sql_dialect=sql_dialect,
    280     return_fids=fid_as_index,
    281     **kwargs,
    282 )
    284 if use_arrow:
    285     meta, table = result

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pyogrio\raw.py:198, in read(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, return_fids, datetime_as_string, **kwargs)
     59 """Read OGR data source into numpy arrays.
     60 
     61 IMPORTANT: non-linear geometry types (e.g., MultiSurface) are converted
   (...)
    194 
    195 """
    196 dataset_kwargs = _preprocess_options_key_value(kwargs) if kwargs else {}
--> 198 return ogr_read(
    199     get_vsi_path_or_buffer(path_or_buffer),
    200     layer=layer,
    201     encoding=encoding,
    202     columns=columns,
    203     read_geometry=read_geometry,
    204     force_2d=force_2d,
    205     skip_features=skip_features,
    206     max_features=max_features or 0,
    207     where=where,
    208     bbox=bbox,
    209     mask=_mask_to_wkb(mask),
    210     fids=fids,
    211     sql=sql,
    212     sql_dialect=sql_dialect,
    213     return_fids=return_fids,
    214     dataset_kwargs=dataset_kwargs,
    215     datetime_as_string=datetime_as_string,
    216 )

File pyogrio\\_io.pyx:1240, in pyogrio._io.ogr_read()

File pyogrio\\_io.pyx:220, in pyogrio._io.ogr_open()

DataSourceError: 11111\sch-aibga-grushevn_pp_orz-new_MSK23_2.shp: No such file or directory

3. Поворотные точки градусы, минуты, секунды в ГСК 2011 (для проверки по ГЕОЛОГИИ)

In [1]:
import geopandas as gpd
import pandas as pd
from os.path import splitext
import os

Функции

In [2]:
# Функция создания списка шейпфайлов для обработки
def shp_lst(folder):
    lst = []
    for file in os.listdir(folder):
        if file.endswith(".shp"):
            lst.append(os.path.join(folder, file))
    print(lst[:2], '\n', f'Всего: {len(lst)} файл(-а, -ов)')
    return lst
In [3]:
# Создает таблицу с координатами градусы-минуты-секунды для полигона в GeoDataFrame (должен быть один объект)
def coordinates(gdf):
    df = gdf.get_coordinates()
    # Добавляем индекс к таблице
    df.reset_index(inplace = True, drop = True)
    # Первая строка с 1
    df.index += 1
    df = df.reset_index()
    
    df['latitude_deg'] = df['y'].astype(int)
    df['latitude_min_full'] = ((df['y'] - df['latitude_deg']) * 60).abs()
    df['latitude_min'] = df['latitude_min_full'].astype(int)
    df['latitude_sec'] = round((df['latitude_min_full'] - df['latitude_min']) * 60, 3)

    df['longitude_deg'] = df['x'].astype(int)
    df['longitude_min_full'] = ((df['x'] - df['longitude_deg']) * 60).abs()
    df['longitude_min'] = df['longitude_min_full'].astype(int)
    df['longitude_sec'] = round((df['longitude_min_full'] - df['longitude_min']) * 60, 3)

    df.drop(['x', 'y', 'latitude_min_full', 'longitude_min_full'], axis=1, inplace=True)
    df = df.rename(columns={'index': 'N'})

    # Добавляем в конец дубликат первой строки
    df = df.take(list(range(len(df) - 1)) + [0])
    return df
In [4]:
##### Функция шаблона Excel c шапкой для координат ГСК-2011

from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, Border, Side

def template_gc(output_file):
    wb = Workbook()
    ws = wb.active

    # --- Первая строка: заголовок "ГСК-2011", объединённый над всеми столбцами ---
    max_col = 7  # например, у вас 7 столбцов в шапке ниже

    ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=max_col)
    header_cell = ws.cell(row=1, column=1, value='ГСК-2011')
    header_cell.font = Font(bold=False)  # простой шрифт (без жирности)
    header_cell.alignment = Alignment(horizontal='center', vertical='center')
    
    # --- Вторая и третья строки: многоуровневая шапка ---
    # Вторая строка — первый уровень шапки
    ws['A3'] = "№ точки"
    ws['B3'] = "Широта [-90:90]\n(С.Ш. - положительные\nЮ.Ш. - отрицательные)"
    ws['E3'] = "Долгота [-180:180]\n(В.Д. - положительные\nЗ.Д. - отрицательные)"

    # Объединение ячеек для первого уровня заголовков
    ws.merge_cells('A3:A4')    # "№ точки" занимает две строки вертикально
    ws.merge_cells('B3:D3')    # "Широта ..." над 3 колонками
    ws.merge_cells('E3:G3')    # "Долгота ..." над 3 колонками

    # Третья строка — второй уровень (подзаголовки) с обычным шрифтом
    sub_headers = [
        'Градусы, \nцелое',
        'Минуты, \nцелое',
        'Секунды,\nдробь до \n3 знаков'
    ]

    cols_lat = ['B', 'C', 'D']
    cols_lon = ['E', 'F', 'G']

    for col, text in zip(cols_lat, sub_headers):
        c = ws[f"{col}4"]
        c.value = text
        c.font = Font(bold=False)  # простой шрифт
        c.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

    for col, text in zip(cols_lon, sub_headers):
        c = ws[f"{col}4"]
        c.value = text
        c.font = Font(bold=False)
        c.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

    # Выравнивание и жирность для второго уровня заголовка (строка 2)
    for cell in ['A3', 'B3', 'E3']:
        c = ws[cell]
        c.font = Font(bold=True)
        c.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

    # Установим высоту строк для переносов текста
    ws.row_dimensions[1].height = 20
    ws.row_dimensions[2].height = 8
    ws.row_dimensions[3].height = 48
    ws.row_dimensions[4].height = 40

    # Подгонка ширины столбцов
    col_widths = [10, 15, 15, 20, 15, 15, 20]
    for i, width in enumerate(col_widths, 1):
        ws.column_dimensions[chr(64 + i)].width = width

    # Добавление рамки по всей области шапки (с 1 по 3 строку, с A по G)
    thin_side = Side(border_style="thin", color="000000")
    border = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)
    for row in ws.iter_rows(min_row=3, max_row=4, min_col=1, max_col=max_col):
        for cell in row:
            # Центрируем все ячейки по горизонтали и вертикали, если текст в ячейке есть
            if cell.value is not None:
                cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
            # Устанавливаем рамку
            cell.border = border

    # Сохраняем файл
    wb.save(output_file)
In [5]:
# Функция форматирования файла Excel, выравнивание всех ячеек по центру и добавления первой строки с номером участка
from openpyxl import load_workbook
from openpyxl.styles import Alignment, Font, Border, Side

def format_xlsx(xlsx, counter): # counter - текущий номер участка
    # Открываем существующий файл
    wb = load_workbook(xlsx)
    ws = wb.active  # или wb['SheetName']

    # Определяем размер текущей таблицы
    min_row, max_row = ws.min_row, ws.max_row
    min_col, max_col = ws.min_column, ws.max_column

# УБРАЛ БЛОК ДОБАВЛЕНИЯ ПЕРВОЙ СТРОКИ С НОМЕРОМ УЧАСТКА, ТАК КАК В СИСТЕМУ МОЖНО ЗАГРУЖАТЬ ТОЛЬКО ПО ОДНОМУ УЧАСТКУ
#     # Вставляем пустую первую строку
#     ws.insert_rows(1)
#     max_row += 1  # сдвигаем нижнюю границу таблицы вниз на 1, т.к. вставили строку

#     # Объединяем все ячейки первой строки в одну ячейку
#     merge_range = f"{ws.cell(row=1, column=min_col).coordinate}:" \
#                   f"{ws.cell(row=1, column=max_col).coordinate}"
#     ws.merge_cells(merge_range)

#     header_cell = ws.cell(row=1, column=min_col)
#     header_cell.value = "Участок " + str(counter)
#     header_cell.font = Font(bold=True, size=11)
#     header_cell.alignment = Alignment(horizontal='center', vertical='center')

#     # Устанавливаем высоту первой строки для читаемости
#     ws.row_dimensions[1].height = 20

    # Создаем стиль тонкой рамки
    thin_side = Side(border_style="thin", color="000000")
    thin_border = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)

    # Проходим по всем ячейкам таблицы (включая новую первую строку)
    for row in ws.iter_rows(min_row=0, max_row=max_row, min_col=min_col, max_col=max_col):
        for cell in row:
            # Центрируем содержимое ячеек
            if cell.value is not None:
                cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
            # Устанавливаем тонкую рамку в каждую ячейку
            cell.border = thin_border

    # Сохраняем в тот же файл
    wb.save(xlsx)
In [6]:
# Функция объединения 2+ файлов-участков от одного ООПТ 
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, PatternFill, Border, Alignment, Protection

def copy_cell_style(src_cell, tgt_cell):
    if src_cell.has_style:
        tgt_cell.font = src_cell.font.copy()
        tgt_cell.border = src_cell.border.copy()
        tgt_cell.fill = src_cell.fill.copy()
        tgt_cell.number_format = src_cell.number_format
        tgt_cell.protection = src_cell.protection.copy()
        tgt_cell.alignment = src_cell.alignment.copy()

def concat_xlsx_with_style(files, output_file):
    wb_new = Workbook()
    ws_new = wb_new.active
    ws_new.title = "Угловые точки DD⁰MM'SS.SS"
    
    current_row = 1
    for idx, file in enumerate(files):
        wb_src = load_workbook(file)
        ws_src = wb_src.active
        
        min_row = 1  # Можно поменять на 2 для пропуска заголовков во 2-м и последующих файлах
        
        # Копируем значения и стили по строкам
        for r in range(min_row, ws_src.max_row + 1):
            for c in range(1, ws_src.max_column + 1):
                src_cell = ws_src.cell(row=r, column=c)
                tgt_cell = ws_new.cell(row=current_row, column=c)
                tgt_cell.value = src_cell.value
                copy_cell_style(src_cell, tgt_cell)
            current_row += 1

        # Копируем объединённые ячейки (merged cells) с учётом сдвига строк
        row_offset = current_row - (ws_src.max_row + 1)
        for merged_range in ws_src.merged_cells.ranges:
            start_row = merged_range.min_row + row_offset
            end_row = merged_range.max_row + row_offset
            start_col = merged_range.min_col
            end_col = merged_range.max_col
            
            new_range = f"{ws_src.cell(row=start_row, column=start_col).coordinate}:" \
                        f"{ws_src.cell(row=end_row, column=end_col).coordinate}"
            try:
                ws_new.merge_cells(new_range)
            except Exception as e:
                print(f"Ошибка объединения: {new_range} - {e}")

        # Копирование высоты строк с учётом сдвига
        for r in range(min_row, ws_src.max_row + 1):
            height = ws_src.row_dimensions[r].height
            if height is not None:
                ws_new.row_dimensions[r + row_offset].height = height

    wb_new.save(output_file)
In [7]:
# Функция добавления листа с координатной системой СК-2011 для добавления на сайт с геологической информацией https://efgi.ru/
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter

def add_cs(excel_path):
    # Данные для записи
    headers = ["СК", "EPSG", "Код в Proj4"]
    values = [
        "ГСК-2011",
        7683,
        "+proj=longlat +a=6378136.5 +rf=298.2564151 "
        "+towgs84=0.013,-0.092,-0.03,-0.001738,0.003559,-0.004263,0.0074 +no_defs"
    ]

    # Загружаем книгу
    wb = load_workbook(excel_path)
    
    # Если лист с именем "СК" существует - удаляем его (чтобы заменить)
    if "СК" in wb.sheetnames:
        del wb["СК"]

    # Создаём новый лист "СК"
    ws = wb.create_sheet("СК")

    # Записываем заголовки в первую строку
    for col_num, header in enumerate(headers, start=1):
        cell = ws.cell(row=1, column=col_num, value=header)

    # Записываем данные во вторую строку
    for col_num, value in enumerate(values, start=1):
        ws.cell(row=2, column=col_num, value=value)
    
    # Сохраняем файл
    wb.save(excel_path)
In [8]:
# Словарь с названиями районов и ООПТ
dic_oopt = {
'anp-anap-soch-skal_pp_orz-new_WGS84': 'Анапа_Анапские сочащиеся скалы',
'anp-stepi-balki-zozulina_pp_orz-new_WGS84': 'Анапа_Степи балки Зозулина',
'anp-bujor-kodzor-les_orz-new_WGS84': 'Анапа_ПП Бужоро-Кодзорский лес',
'anp-krasnogor-les_pp_orz-new_WGS84': 'Анапа_ПП Красногорский лес',
'anp-kumatyr-hrebet_zak_orz_WGS84': 'Анапа_ПП Куматырский лес',
'anp-shkurat-les_orz-new_WGS84': 'Анапа_ПП Шкуратский лес',
'anp-yug-gostag-les_orz-new_WGS84': 'Анапа_ПП Южно-Гостагаевскй лес',
'anp-kurb-bugor_pp_orz-new_WGS84': 'Анапа_ПП Курбацкие бугры',
'anp-chekon-redkoles_orz-new_WGS84': 'Анапа_ПП Чеконское редколесье',
'brh-vys-bereg-beysug_pp_orz-new_WGS84': 'Брюховецкий_район_ПП_Высокий берег реки Бейсуг',
'glj-gebeus-gorn-mass_zak_orz-new_WGS84': 'Геленджик_ПП_Гебеусский горный массив',
'glj-mojj-redkles-gor-neksis_pp_orz-new_WGS84': 'Геленджик_ПП_Можжевеловые редколесья хребта Нексис',
'esk-voronts-step_pp_orz-new_WGS84': 'Ейский район_Воронцовская степь',
'esk-kruglaya-pad_pp_orz-new_WGS84': 'Ейский район_Круглая падь',
'esk-hans-yasen_zak_orz-new_WGS84': 'Ейский район_Ханско-Ясенский заказник',
'esk-yasnopol-step_pp_orz-new_WGS84': 'Ейский район_Яснопольская_степь',
'kvk-obrivistiy-bereg-reki-kuban_pp_orz-new_WGS84': 'Кавказский и Тбилисск район_Обрывистый берег реки Кубань',
'kvk-temijbek-vysok-bereg_pp_orz-new_WGS84': 'Кавказский_район_ПП_Темижбекский высокий берег',
'knv-bolot-albashi_lpk_orz-new_WGS84': 'Каневской_район_ ЛПК Болото Албаши',
'knv-chelbas-plavn_lpk_orz-new_WGS84': 'Каневской_район_ЛПК Челбасские плавни',
'knv-nijn-beisug-plavn_lpk_orz-new_WGS84': 'Каневской_район_ЛПК Нижнебейсугские плавни',
'kln-ponur-les_pp-orz-new_WGS84': 'Калининский_район_ПП Понурский лес',
'kln-ponura-liman_lpk_orz-new_WGS84': 'Калининский_район_ЛПК Понурский лиман',
'krl-balk-irinovka+_pp_orz-new_WGS84': 'Крыловский_район_ПП_Балка Ириновка',
'krl-balk-krutaya_pp_orz-new_WGS84': 'Крыловский район_Балка Крутая в долине реки Ея',
'krl-gryadin-plavn-rek-eya_pp_orz-new_WGS84': 'Крыловский_район_ПП_Грядинские плавни реки Ея',
'krl-balk-irinovka+_pp_orz-new_WGS84': 'Крыловский_район_ПП_Балка Ириновка',
'krm-uroch_kovalenkova_baga_pp_orz-new_WGS84': 'Крымский район_Коваленкова бага',
'ksh-poltavskiy-ostanec_pp_orz-new_WGS84': 'Кущевский район_Полтавский останец степи в долине реки Эльбузд',
'ksh-uroch-alekseevskoe_pp_orz-new_WGS84': 'Кущевский район_Урочище Алексеевское',
'ksh-shalfey-step-rek-elbuzd_pp_orz-new_WGS84': 'Кущевский_район_ПП_Шалфеевая_степь',
'mst-besleneev-pesheri_pp_orz-new_WGS84': 'Мостовской_р-н_ПП_Пещера Бесленеевская',
'nvk-kutanskaya-step_pp_orz-new_WGS84': 'Успенск_Новокуб_Армавир_р-н_ПП_Кутанская степь',
'nvp-fedorenk-bugr_pp_orz-new_WGS84': 'Новопокровский_район_ПП_Федоренковы бугры',
'nvk-fortshtadt-nagorn-step_pp_orz-new_WGS84': 'Армавир_Новокубанск_р-н_ПП_Фортштадская степь',
'nvr-abrau_zak+_orz-new_WGS84': 'Новороссийск_Абраусский заказник',
'nvr-abrau_zak_orz-IZMENENIE_WGS84': 'Новороссийск_Абраусский заказник_расширение Щурова',
'nvr-atakay-markoth_zak_orz-new_WGS84': 'Новороссийск_Атакайско-Маркотхский',
'nvr-kovilskaya-step_pp_orz-new_WGS84': 'Новороссийск_Ковыльская_степь_хребта_Сенетх',
'nvr-petrof-step-hr-seneth_pp_orz-new_WGS84': 'Новороссийск_Петрофитные_степи_Гора Самбурова',
'otr-dzheltmesskie-vysoty_zak_orz-new_WGS84': 'Отрадненский район_Джельтмесские Высоты',
'otr-chehrak+_zak_orz-new_WGS84': 'Отрадненский район_заказник Чехрак',
'otr-chehrak+_zak_orz-IZMENENIE_WGS84': 'Отрадненский район_заказник Чехрак_изменения ООПТ',
'pvl-urch-mugan_pp_orz-new_WGS84': 'Павловский_район_ПП_Урочище Муган',
'svr-hrebet-dlinn-gor_prz_orz-new_WGS84': 'Cеверский_район_ПРЗ Хребет Длинная гора',
'sch-aibga-grushevn_pp_orz-new_WGS84': 'Сочи_ПП_Аибгинский грушевник',
'sch-mamay-lesopark_zak_orz-new_WGS84': 'Сочи_Мамайский лесопарк',
'sch-urch-dzhegosh_pp_orz-new_WGS84': 'Сочи_ПП_Урочище Джегош',
'tmr-urochische-strelka-yuzhnaya_pp_orz-new_WGS84': 'Темрюкский район_Урочище Стрелка Южная',
'tbl-tbiliss_visokiy_bereg_pp_orz-new_WGS84': 'Тбилисский_р-н_ПП_Тбилисский высокий берег',
'tbl-savk-kruch_pp_orz-new_WGS84': 'Тбилисский_р-н_ПП_Савкины кручи',
'tps-gebeus-gorn-mass_zak_orz-new_WGS84': 'Туапсинский район_ПП_Гебеусский горный массив',
'usp-stepnoy_zak++_orz-new_WGS84': 'Успенский_р-н_заказник Степной',
'usp-uspen-poim-les_zak_orz-new_WGS84': 'Успенский_Отрадн_р-н_ПП_Успенские пойменные леса',
'ulb-uch-lesostep-rek-2-zelench_pp_orz-new_WGS84': 'Усть-Лаб_р-н_ПП_Участки лесостепи на р. Зеленчук',
'ulb-urochische-ladozhskoe_pp_orz-new_WGS84': 'Усть-Лабинский район_Урочище Ладожское',
'ulb-urochische-pyatihatskoe_pp_orz-new_WGS84': 'Усть-Лабинский район_Урочище Пятихатское',
'shr-eisk-plavn_lpk_orz-new_WGS84': 'Щербиновский район_ЛПК Ейские плавни',
'shr-uroch-tureckaya-krepost_ppk_orz-new_WGS84': 'Щербиновский район_ППК Турецкая крепость',
}

Начало обработки

In [9]:
## Список файлов
folder = 'Input_processing' # папка для обработки

lst = shp_lst(folder)
['Input_processing\\tbl-tbiliss_visokiy_bereg_pp_orz-new_WGS84.shp', 'Input_processing\\Орнитологический Границы тропы.shp'] 
 Всего: 2 файл(-а, -ов)
In [10]:
# Пакетная обработка
for shp in lst:
    # Считываем файл
    gdf = gpd.read_file(shp) 
    gdf = gdf.to_crs(7683) # Переводим в ГСК-2011
    # Извлекаем имя файла
    shp_name = os.path.basename(shp).split('.')[0]
    # Счетчик для сохранения разных участков одной ООПТ
    counter = 0
    
    # Превращаем возможных мултисоставной объект (multipart) в односоставной (singlepart)
    gdf = gdf.explode(index_parts=False).reset_index(drop=True)
    
    # Создает список фреймов из каждой объекта (если многосоставной) 
    list_of_single_row_gdfs = [gdf.iloc[[i]] for i in range(len(gdf))]

    
    for el in list_of_single_row_gdfs:
        # Счетчик для номера участка внутри одного шейпа
        counter += 1
        merge_lst = []
        # Вызов функции для расчета и форматирования координат
        df = coordinates(el)
        
        name_xlsx = shp_name + '.xlsx'
        df.to_excel(name_xlsx, header=False, index=False)
        
        # Форматируем файл через функцию
        format_xlsx(name_xlsx, counter) 
        merge_lst.append(name_xlsx)
    
        # Создаем шапку для таблицы (функция), если файл не существует
        if not os.path.exists('template_gc.xlsx'):
            template_gc('template_gc.xlsx')     
        
        # Добавлеем шапку таблицы в список файлов для обработки под номером 0
        merge_lst.insert(0, 'template_gc.xlsx')
        
        # Имя из словаря со списком район+ООПТ по имени shape-файла
        final_name = str('Координаты_') + dic_oopt[shp_name] + "_Участок_" + str(counter) + '.xlsx'
        
        # Объединяем все файлы из списка с сохранением исходного форматирования (функция)
        concat_xlsx_with_style(merge_lst, final_name)
        
        # Добавляем второй лист с параметрами преобразования координат для геологического сервиса
        add_cs(final_name)
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:7: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.font = src_cell.font.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:8: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.border = src_cell.border.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:9: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.fill = src_cell.fill.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:11: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.protection = src_cell.protection.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:12: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.alignment = src_cell.alignment.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:7: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.font = src_cell.font.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:8: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.border = src_cell.border.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:9: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.fill = src_cell.fill.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:11: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.protection = src_cell.protection.copy()
C:\Users\Evgenii\AppData\Local\Temp\ipykernel_43168\1470149036.py:12: DeprecationWarning: Call to deprecated function copy (Use copy(obj) or cell.obj = cell.obj + other).
  tgt_cell.alignment = src_cell.alignment.copy()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[10], line 40
     37 merge_lst.insert(0, 'template_gc.xlsx')
     39 # Имя из словаря со списком район+ООПТ по имени shape-файла
---> 40 final_name = str('Координаты_') + dic_oopt[shp_name] + "_Участок_" + str(counter) + '.xlsx'
     42 # Объединяем все файлы из списка с сохранением исходного форматирования (функция)
     43 concat_xlsx_with_style(merge_lst, final_name)

KeyError: 'Орнитологический Границы тропы'

4. Поворотные точки с 3 координатными системами (WGS84, МСК23, ГСК2011) - АНАПСКАЯ ПЕРЕСЫПЬ

In [56]:
import geopandas as gpd
import pandas as pd
from os.path import splitext
import os
In [57]:
# Функция создания списка шейпфайлов для обработки
def shp_lst(folder):
    lst = []
    for file in os.listdir(folder):
        if file.endswith(".shp"):
            lst.append(os.path.join(folder, file))
    print(lst[:2], '\n', f'Всего: {len(lst)} файл(-а, -ов)')
    return lst

### Функция создания таблицы с коодинатными системами
# Извлекаем имя файла
def coord_crs(shp):
    shp_name = os.path.splitext(os.path.basename(shp))[0]
    gdf = gpd.read_file(shp)
       
    # WGS84
    gdf1 = gdf.to_crs(4326)
    # Извлекаем координаты вертексов полигона (формат X, Y в текущей координатной системе)
    df1 = gdf1.get_coordinates()
    # Добавляем индекс к таблице
    df1.reset_index(inplace = True, drop = True)
    # Первая строка с 1
    df1.index += 1
    df1 = df1.reset_index()
    df1 = round(df1,6)
    df1.columns = ['N', 'Dolgota_X_WGS84', 'Shirota_Y_WGS84', ]
    df1 = df1[['N', 'Shirota_Y_WGS84', 'Dolgota_X_WGS84']]

    #MSK23
    gdf2 = gdf.to_crs(msk23_1)
    df2 = gdf2.get_coordinates()
    # Добавляем индекс к таблице
    df2.reset_index(inplace = True, drop = True)
    # Первая строка с 1
    df2.index += 1
    df2 = df2.reset_index()
    # Округляем
    df2 = round(df2)
    df2.columns = ['N', 'Dolgota_X_MSK23', 'Shirota_Y_MSK23', ]
    df2 = df2[['N', 'Shirota_Y_MSK23', 'Dolgota_X_MSK23']]

    #GSK2011
    gdf3 = gdf.to_crs(7683)
    df3 = gdf3.get_coordinates()
    # Добавляем индекс к таблице
    df3.reset_index(inplace = True, drop = True)
    # Первая строка с 1
    df3.index += 1
    df3 = df3.reset_index()
    df3 = round(df3,6)
    df3.columns = ['N', 'Dolgota_X_GSK2011', 'Shirota_Y_GSK2011',]
    df3 = df3[['N', 'Shirota_Y_GSK2011','Dolgota_X_GSK2011']]

    # Объединение таблиц с координатами (df1 и df2)
    df1 = df1.merge(df2,left_on='N', right_on='N')
    df1 = df1.merge(df3,left_on='N', right_on='N')
    # Добавляем в конец дубликат первой строки
    df1 = df1.take(list(range(len(df1) - 1)) + [0])
    # Файлы Excel помещаются в папку скрипта с именем исходном файла
    df1.to_excel(shp_name + '.xlsx', index=False)
In [58]:
# Считываем проекцию в формате WKT и сохраняем ее в переменной
from pyproj import CRS
with open(r'F:\PROJECT-Colormap\Преобразования географических проекций (СК, ГСК-2011, МСК)\МСК23 проекции Краснодар\MCK-23_Zone_1N.prj', 'r') as f:
    #wkt = f.read()
    msk23_1 = f.read()
# crs = CRS.from_wkt(wkt)

with open(r'F:\PROJECT-Colormap\Преобразования географических проекций (СК, ГСК-2011, МСК)\МСК23 проекции Краснодар\MCK-23_Zone_2N.prj', 'r') as f:
    msk23_2 = f.read()
In [59]:
## Список файлов
folder = 'Input_processing' # папка для обработки

lst = shp_lst(folder)
['Input_processing\\Орнитологический Границы тропы.shp'] 
 Всего: 1 файл(-а, -ов)
In [60]:
# Обработка 
for shp in lst:
    coord_crs(shp)

Тоже, что и выше, но создают для каждого элемента в шейпе отдельный файл Excel и название файла по номеру кад. участка (делал через Perplexity)

In [55]:
import geopandas as gpd
import pandas as pd
from os.path import splitext, basename, join
import os
from pyproj import CRS

# ---------- Настройки ----------
INPUT_FOLDER = 'Input_processing'  # папка для обработки
OUTPUT_FOLDER = 'Output_excel'     # куда сохранять Excel
CAD_FIELD = 'cad_number'           # имя столбца с кадастровым номером
MSK23_WKT_FILE = r'F:\PROJECT-Colormap\Преобразования географических проекций (СК, ГСК-2011, МСК)\МСК23 проекции Краснодар\MCK-23_Zone_1N.prj'

# ---------- Вспомогательные ----------
def ensure_dir(path):
    os.makedirs(path, exist_ok=True)

def shp_lst(folder):
    lst = [os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith(".shp")]
    print(lst[:2], '\n', f'Всего: {len(lst)} файл(-а, -ов)')
    return lst

def load_wkt(path):
    with open(path, 'r', encoding='utf-8') as f:
        return f.read()

# ---------- Основная функция ----------
def export_features_to_excels(shp, msk23_wkt, output_dir=OUTPUT_FOLDER, cad_field=CAD_FIELD):
    ensure_dir(output_dir)
    shp_name = os.path.splitext(os.path.basename(shp))[0]
    gdf = gpd.read_file(shp)

    if cad_field not in gdf.columns:
        print(f'WARNING: {cad_field} не найден в {shp_name}. Будут использованы имена по индексу.')

    # Преобразования систем координат заранее
    gdf_wgs = gdf.to_crs(4326)        # WGS84
    gdf_msk = gdf.to_crs(msk23_wkt)    # MSK23 WKT
    gdf_gsk = gdf.to_crs(7683)         # GSK2011 / EPSG:7683

    # Проходим по каждому объекту
    for i, (wgs_geom, msk_geom, gsk_geom, row) in enumerate(zip(gdf_wgs.geometry, gdf_msk.geometry, gdf_gsk.geometry, gdf.itertuples(index=False))):
        # Имя файла
        base_name = getattr(row, cad_field) if cad_field in gdf.columns else f'{shp_name}_feat_{i+1}'
        base_name = str(base_name).replace(':', '_')

        # Собираем координаты вершин полигона/мультиполигона
        def coords_as_df(geom, x_name, y_name, round_ndigits=None, round0=False):
            # Разворачиваем в список колец
            def extract_rings(g):
                if g is None or g.is_empty:
                    return []
                if g.geom_type == 'Polygon':
                    return [g.exterior] + list(g.interiors)
                if g.geom_type == 'MultiPolygon':
                    rings = []
                    for p in g.geoms:
                        rings.append(p.exterior)
                        rings.extend(list(p.interiors))
                    return rings
                # Для LineString/Point fallback
                return [g]
            pts = []
            for ring in extract_rings(geom):
                for x, y in ring.coords:
                    pts.append((y, x))  # хотим сначала Y, потом X
            df = pd.DataFrame(pts, columns=[y_name, x_name]).reset_index(drop=True)
            df.index += 1
            df.insert(0, 'N', df.index)
            if round0:
                df[[y_name, x_name]] = df[[y_name, x_name]].round(0)
            elif round_ndigits is not None:
                df[[y_name, x_name]] = df[[y_name, x_name]].round(round_ndigits)
            # Добавляем дубликат первой строки в конец
            if len(df) > 0:
                first_row = df.iloc[[0]].copy()
                df = pd.concat([df.iloc[:-1], first_row], ignore_index=True)
                df.index += 1
                df['N'] = df.index
            return df

        df_wgs = coords_as_df(wgs_geom, 'Dolgota_X_WGS84', 'Shirota_Y_WGS84', round_ndigits=6)
        df_msk = coords_as_df(msk_geom, 'Dolgota_X_MSK23', 'Shirota_Y_MSK23', round0=True)
        df_gsk = coords_as_df(gsk_geom, 'Dolgota_X_GSK2011', 'Shirota_Y_GSK2011', round_ndigits=6)

        # Объединяем по N
        df = df_wgs.merge(df_msk, on='N', how='outer').merge(df_gsk, on='N', how='outer')

        # Сохраняем в Excel
        out_path = os.path.join(output_dir, f'{base_name}.xlsx')
        df.to_excel(out_path, index=False)

    print(f'Готово: {shp_name} -> {output_dir}')

# ---------- Инициализация ----------
msk23_wkt = load_wkt(MSK23_WKT_FILE)

# ---------- Запуск ----------
for shp in shp_lst(INPUT_FOLDER):
    export_features_to_excels(shp, msk23_wkt)
['Input_processing\\selected_cad_numbers.shp'] 
 Всего: 1 файл(-а, -ов)
Готово: selected_cad_numbers -> Output_excel

5. Преобразование в MapInfo

In [ ]:
import geopandas as gpd
import pandas as pd
from os.path import splitext
import os

# Функция создания списка шейпфайлов для обработки
def shp_lst(folder):
    lst = []
    for file in os.listdir(folder):
        if file.endswith(".shp"):
            lst.append(os.path.join(folder, file))
    print(lst[:2], '\n', f'Всего: {len(lst)} файл(-а, -ов)')
    return lst
In [48]:
### Перевод в MapInfo
folder = 'Output'
for shp in lst:
    shp_name = os.path.splitext(os.path.basename(shp))[0]
    gdf = gpd.read_file(shp)
    gdf.to_file(folder + '\\' + shp_name, driver='MapInfo File')