infrapy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
infrapy/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ # src/infrapy/__init__.py
2
+
3
+ from . import config
4
+ from . import cores
5
+ from . import excel
6
+ from . import export
7
+ from . import geo
8
+ from . import mapas
9
+ from . import sharepoint
10
+ from . import interface
11
+
12
+ __version__ = "0.1.0"
infrapy/config.py ADDED
@@ -0,0 +1,131 @@
1
+ """
2
+ config.py - ponto unico onde o pacote le configuracao/segredos do ambiente.
3
+
4
+ Nenhuma credencial ou caminho pessoal fica escrito aqui. Tudo vem do .env
5
+ (carregado no __init__.py). Estruturas compostas (ex.: o dicionario de
6
+ credenciais do certificado) sao MONTADAS aqui a partir das variaveis
7
+ simples lidas do .env.
8
+ """
9
+
10
+ if True: # Bibliotecas
11
+ import os
12
+
13
+
14
+ if True: # Funcoes que leem o .env
15
+ def _get(nome, obrigatorio=True, default=None) -> str | None:
16
+ """
17
+ Le uma variavel de ambiente (vinda do .env).
18
+
19
+ Parameters
20
+ ----------
21
+ nome : str
22
+ Nome da variavel no .env.
23
+ obrigatorio : bool, optional
24
+ Se True, levanta erro quando a variavel esta ausente. Padrao True.
25
+ default : str, optional
26
+ Valor retornado quando a variavel nao existe e nao e obrigatoria.
27
+
28
+ Returns
29
+ -------
30
+ str or None
31
+ Valor da variavel, ou o default.
32
+
33
+ Raises
34
+ ------
35
+ RuntimeError
36
+ Se a variavel for obrigatoria e nao estiver definida.
37
+ """
38
+ valor = os.environ.get(nome, default)
39
+ if obrigatorio and not valor:
40
+ raise RuntimeError(
41
+ f"[infrapy] variavel '{nome}' nao encontrada. "
42
+ f"Adicione-a ao seu .env. Veja .env.example."
43
+ )
44
+ return valor
45
+
46
+ # ---- Caminhos padrao ----
47
+ def PATH_ACC() -> str | None:
48
+ """Caminho base do projeto ACC. Retorna str ou None."""
49
+ return _get("ACC_PATH", obrigatorio=False)
50
+
51
+ def PATH_SHAREPOINT() -> str | None:
52
+ """Caminho local ate a pasta do SharePoint. Retorna str ou None."""
53
+ return _get("SHAREPOINT_PATH", obrigatorio=False)
54
+
55
+ def PATH_REDE() -> str | None:
56
+ """Caminho de rede padrao do projeto na Infra. Retorna str ou None."""
57
+ return _get("REDE_PATH", obrigatorio=False)
58
+
59
+ def PATH_BASE_ALN() -> str | None:
60
+ """Caminho padrão da base de alinhamentos. Retorna str ou None."""
61
+ return _get("BASE_ALINHAMENTOS", obrigatorio=False)
62
+
63
+ # ---- SharePoint ----
64
+ def SHAREPOINT_USER() -> str:
65
+ """E-mail/usuario do SharePoint. Retorna str."""
66
+ return _get("INFRA_USER")
67
+
68
+ def SHAREPOINT_PASSWORD() -> str:
69
+ """Senha do SharePoint. Retorna str."""
70
+ return _get("INFRA_SENHA")
71
+
72
+ def SHAREPOINT_SITE() -> str:
73
+ """URL do site do SharePoint. Retorna str."""
74
+ return _get("INFRA_SITE")
75
+
76
+ def SHAREPOINT_URL() -> str:
77
+ """URL relativa base dos documentos no SharePoint. Retorna str."""
78
+ return _get("INFRA_URL", obrigatorio=False)
79
+
80
+ # ---- Azure / certificado ---- APENAS TI PODE GERAR/FORNECER
81
+ def SHAREPOINT_TENANT_ID() -> str:
82
+ """ID do Tenant(SITE) do sharepoint. Retorna str."""
83
+ return _get("INFRA_AZURE_TENANT_ID", obrigatorio=False)
84
+
85
+ def SHAREPOINT_CLIENT_ID() -> str:
86
+ """ID da aplicação do Azure. Retorna str."""
87
+ return _get("INFRA_AZURE_CLIENT_ID", obrigatorio=False)
88
+
89
+ def SHAREPOINT_CERTIFICATE_THUMBPRINT() -> str:
90
+ """Certificado da aplicação do Azure. Retorna str."""
91
+ return _get("CERT_THUMBPRINT", obrigatorio=False)
92
+
93
+ def SHAREPOINT_CERTIFICATE_KEY() -> str:
94
+ """Caminho da "key.pem" do Azure. Retorna str."""
95
+ return _get("CERT_KEY", obrigatorio=False)
96
+
97
+ def SHAREPOINT_CERTIFICATE() -> dict:
98
+ """
99
+ Monta o dicionario de credenciais de app do Azure (autenticacao por
100
+ certificado), a partir das variaveis simples do .env.
101
+
102
+ Returns
103
+ -------
104
+ dict
105
+ Dicionario com as chaves tenant, client_id, thumbprint e cert_path,
106
+ no formato esperado pelo Office365-REST-Python-Client.
107
+ """
108
+ return {
109
+ "tenant": _get("INFRA_AZURE_TENANT_ID"),
110
+ "client_id": _get("INFRA_AZURE_CLIENT_ID"),
111
+ "thumbprint": _get("CERT_THUMBPRINT"),
112
+ "cert_path": _get("CERT_KEY"),
113
+ }
114
+
115
+ # ---- DBeaver / PostgreSQL ----
116
+ def DBEAVER_CONFIG() -> dict:
117
+ """
118
+ Retorna as credenciais de conexao ao banco (DBeaver/PostgreSQL).
119
+
120
+ Returns
121
+ -------
122
+ dict
123
+ Dicionario com user, password, host, port e database.
124
+ """
125
+ return {
126
+ "user": _get("DBEAVER_USER"),
127
+ "password": _get("DBEAVER_PASSWORD"),
128
+ "host": _get("DBEAVER_HOST"),
129
+ "port": _get("DBEAVER_PORT"),
130
+ "database": _get("DBEAVER_DATABASE"),
131
+ }
infrapy/cores.py ADDED
@@ -0,0 +1,120 @@
1
+ """cores.py - geracao de cores aleatorias e exibicao no Jupyter."""
2
+
3
+ if True: # Bibliotecas
4
+ from IPython.display import HTML, display
5
+ from typing import Iterable
6
+ import random
7
+
8
+ if True: # Funcoes
9
+ def cor_aleatoria() -> str:
10
+ """
11
+ Gera uma cor hexadecimal aleatória.
12
+
13
+ A cor é gerada sorteando um valor inteiro entre ``0x000000`` e
14
+ ``0xFFFFFF`` e convertendo-o para o formato hexadecimal utilizado em CSS.
15
+
16
+ Returns
17
+ -------
18
+ str
19
+ Código hexadecimal da cor no formato ``"#RRGGBB"``.
20
+
21
+ Examples
22
+ --------
23
+ >>> cor_aleatoria()
24
+ '#3fa9d2'
25
+ """
26
+ return f"#{random.randint(0, 0xFFFFFF):06x}"
27
+
28
+ def cor_aleatoria_variada() -> str:
29
+ """
30
+ Gera uma cor hexadecimal aleatória com maior saturação.
31
+
32
+ A função limita um dos canais RGB a zero e sorteia os outros dois
33
+ entre 60 e 255, produzindo predominantemente cores mais vibrantes
34
+ e evitando tons muito escuros ou acinzentados.
35
+
36
+ Returns
37
+ -------
38
+ str
39
+ Código hexadecimal da cor no formato ``"#RRGGBB"``.
40
+
41
+ Examples
42
+ --------
43
+ >>> cor_aleatoria_variada()
44
+ '#ff7a00'
45
+ """
46
+ valores_1 = [(60, 255), (0, 0), (60, 255)]
47
+ valores_2 = [(0, 0), (60, 255), (60, 255)]
48
+ valores_3 = [(60, 255), (60, 255), (0, 0)]
49
+
50
+ valores = random.choice([valores_1, valores_2, valores_3])
51
+
52
+ R = random.randint(valores[0][0], valores[0][1])
53
+ G = random.randint(valores[1][0], valores[1][1])
54
+ B = random.randint(valores[2][0], valores[2][1])
55
+
56
+ return f"#{R:02x}{G:02x}{B:02x}"
57
+
58
+ def cor_aleatoria_predef() -> str:
59
+ """
60
+ Retorna uma cor escolhida aleatoriamente de uma paleta predefinida.
61
+
62
+ A paleta contém cores previamente selecionadas, incluindo tons de
63
+ azul, roxo, ciano, magenta, vermelho, amarelo, verde e cores neutras.
64
+
65
+ Returns
66
+ -------
67
+ str
68
+ Código hexadecimal da cor selecionada no formato ``"#RRGGBB"``.
69
+
70
+ Examples
71
+ --------
72
+ >>> cor_aleatoria_predef()
73
+ '#1f7ee6'
74
+ """
75
+ cores_predefinidas = [
76
+ # Tons de azul e roxo
77
+ "#500090", "#3e23b4", "#cb99e1", "#1f7ee6", "#005987", "#55aaff",
78
+ # Tons de ciano
79
+ "#00ffff", "#00b7c7",
80
+ # Tons de magenta
81
+ "#ff00ff", "#ff007f", "#a300a5", "#9600f4", "#aa007f", "#860044", "#ffaaff",
82
+ # Tons de vermelho
83
+ "#ff0000", "#f94305", "#ad2a0d", "#dd4160", "#ff8ba5", "#ffaa7f",
84
+ # Tons de amarelo
85
+ "#ffff00", "#afdb00", "#deec00", "#c5c800", "#c6b200", "#bd9033",
86
+ # Tons de verde
87
+ "#00ff00", "#00ff7f", "#47c700", "#01ed9d", "#00f63d",
88
+ # Tons neutros
89
+ "#ffffff", "#b4f3db", "#add3b1", "#d2af9f", "#979768", "#1a2b35"
90
+ ]
91
+ return random.choice(cores_predefinidas)
92
+
93
+ def exibir_blocos_cores_ipynb(cores: Iterable[str]) -> None:
94
+ """
95
+ Exibe uma paleta de cores em um notebook Jupyter.
96
+
97
+ Cria um conjunto de retângulos coloridos utilizando HTML, onde cada
98
+ retângulo possui a cor especificada e exibe seu respectivo nome ou
99
+ código hexadecimal.
100
+
101
+ Parameters
102
+ ----------
103
+ cores : Iterable[str]
104
+ Iterável contendo nomes de cores CSS ou códigos de cores
105
+ (ex.: "#FF0000", "red", "rgb(0, 128, 255)", (f"#{i:06X}" for i in range(10)) ).
106
+
107
+ Returns
108
+ -------
109
+ None
110
+ A função apenas renderiza o HTML no notebook.
111
+
112
+ Examples
113
+ --------
114
+ >>> exibir_blocos_cores_ipynb(["#FF0000", "#00FF00", "#0000FF", "gold", "royalblue"])
115
+ """
116
+ html = "<div style='display: flex; flex-wrap: wrap;'>"
117
+ for cor in cores:
118
+ html += f"<div style='width: 100px; height: 50px; background-color: {cor}; margin: 5px; text-align: center; line-height: 50px; color: #fff;'>{cor}</div>"
119
+ html += "</div>"
120
+ display(HTML(html))
infrapy/excel.py ADDED
@@ -0,0 +1,147 @@
1
+ """excel.py - leitura de planilhas (xlwings) e exportacao para xlsx."""
2
+
3
+ if True: # Bibliotecas
4
+ import os
5
+ import pandas as pd
6
+ import geopandas as gpd
7
+ import xlwings as xw
8
+
9
+ from . import config
10
+
11
+ if True: # Funcoes
12
+
13
+ def abrir_ou_conectar_excel(caminho: str) -> xw.Book:
14
+ """
15
+ Retorna um objeto Book do xlwings, reaproveitando o arquivo se ele ja
16
+ estiver aberto no Excel, ou abrindo-o do disco caso contrario.
17
+
18
+ Parameters
19
+ ----------
20
+ caminho : str
21
+ Caminho completo do arquivo .xlsx/.xlsb a abrir.
22
+
23
+ Returns
24
+ -------
25
+ xlwings.Book
26
+ O livro correspondente ao arquivo, ja aberto e pronto para uso.
27
+
28
+ Raises
29
+ ------
30
+ FileNotFoundError
31
+ Se o arquivo nao existir no disco e tambem nao estiver aberto.
32
+
33
+ """
34
+ nome_alvo = os.path.basename(caminho).lower()
35
+
36
+ for app in xw.apps:
37
+ for livro in app.books:
38
+ if livro.name.lower() == nome_alvo:
39
+ return livro
40
+
41
+ if not os.path.exists(caminho):
42
+ raise FileNotFoundError(
43
+ f"Arquivo nao encontrado e nao esta aberto no Excel: {caminho}"
44
+ )
45
+
46
+ return xw.Book(caminho)
47
+
48
+ def ler_excel(caminho: str, aba: str | int, celula: str, CRS: str | None = None, expandir: bool = True) -> tuple[xw.Sheet, pd.DataFrame | gpd.GeoDataFrame]:
49
+ """
50
+ Le uma tabela de um arquivo Excel via xlwings.
51
+
52
+ Reaproveita o arquivo se ele ja estiver aberto no Excel, evitando conflitos de instancia.
53
+
54
+ Parameters
55
+ ----------
56
+ caminho : str
57
+ Caminho completo do arquivo Excel.
58
+
59
+ aba : str or int
60
+ Nome ou indice da planilha (aba).
61
+
62
+ celula : str
63
+ Ponto de leitura. Se ``expandir=True``, e a celula ancora onde a
64
+ tabela comeca (ex.: "A1"). Se ``expandir=False``, e o intervalo
65
+ exato a ler (ex.: "A1:F20").
66
+
67
+ CRS : str, optional
68
+ Se informado (ex.: "EPSG:4326"), retorna um GeoDataFrame, criando a
69
+ coluna 'geometry' vazia se ela nao existir. Padrao None (DataFrame).
70
+
71
+ expandir : bool, optional
72
+ Se True (padrao), expande a partir da celula ancora para toda a
73
+ regiao de dados contigua. Se False, le exatamente o intervalo
74
+ informado em ``celula``.
75
+
76
+ Returns
77
+ -------
78
+ tuple of (xlwings.Sheet, pandas.DataFrame or geopandas.GeoDataFrame)
79
+ A planilha (para manipulacao posterior) e os dados lidos. O segundo
80
+ elemento e um GeoDataFrame quando ``CRS`` e informado, senao um
81
+ DataFrame comum.
82
+
83
+ Examples
84
+ --------
85
+ Expandir a partir de uma celula ancora (comportamento padrao):
86
+
87
+ >>> ws, df = ler_excel("dados.xlsx", "Plan1", "A1")
88
+
89
+ Ler um intervalo exato, sem expandir:
90
+
91
+ >>> ws, df = ler_excel("dados.xlsx", "Plan1", "A1:F20", expandir=False)
92
+
93
+ Ler como dado geografico:
94
+
95
+ >>> ws, gdf = ler_excel("snv.xlsx", "SNV", "A1", CRS="EPSG:4326")
96
+ """
97
+ wb = abrir_ou_conectar_excel(caminho)
98
+ ws = wb.sheets(aba)
99
+
100
+ rng = ws.range(celula)
101
+ if expandir:
102
+ rng = rng.expand(mode='table')
103
+
104
+ df = rng.options(pd.DataFrame, index=False, header=1).value
105
+
106
+ if CRS is not None:
107
+ if 'geometry' not in df.columns:
108
+ df['geometry'] = None
109
+ df = gpd.GeoDataFrame(df, crs=CRS, geometry='geometry')
110
+
111
+ return ws, df
112
+
113
+ def exportar_abas(caminho: str, abas: list[str], output: str) -> None:
114
+ """
115
+ Le abas especificas de um arquivo Excel e salva cada uma como um .xlsx
116
+ separado na pasta de saida.
117
+
118
+ Parameters
119
+ ----------
120
+ caminho : str
121
+ Caminho do arquivo Excel de origem.
122
+
123
+ abas : list of str
124
+ Nomes das abas a exportar.
125
+
126
+ output : str
127
+ Pasta de destino. Criada automaticamente se nao existir.
128
+
129
+ Returns
130
+ -------
131
+ None
132
+
133
+ Examples
134
+ --------
135
+ >>> exportar_abas("origem.xlsx", ["Jan", "Fev"], "saida/")
136
+ """
137
+ if not os.path.exists(output):
138
+ os.makedirs(output)
139
+
140
+ for aba in abas:
141
+ df = pd.read_excel(caminho, sheet_name=aba)
142
+ df.dropna(subset=["COD"], inplace=True)
143
+ nome_arquivo = os.path.join(output, f"{aba}.xlsx")
144
+ if os.path.exists(nome_arquivo):
145
+ os.remove(nome_arquivo)
146
+ with pd.ExcelWriter(nome_arquivo, engine='xlsxwriter') as writer:
147
+ df.to_excel(writer, sheet_name=aba, index=False)
infrapy/export.py ADDED
@@ -0,0 +1,42 @@
1
+ """export.py - exportacao de DataFrames/GeoDataFrames para xlsx e shapefile."""
2
+
3
+ if True: # Bibliotecas
4
+ import os
5
+ import json
6
+
7
+ import geopandas as gpd
8
+ from shapely.geometry import shape
9
+
10
+ from . import config
11
+
12
+ if True: # Funcoes
13
+
14
+ def JSON_para_SHP(caminho: str, arquivo: str) -> None:
15
+ """
16
+ Le um GeoJSON e salva como shapefile na mesma pasta.
17
+
18
+ Parameters
19
+ ----------
20
+ caminho : str
21
+ Pasta onde esta o arquivo .json (e onde o .shp sera salvo).
22
+ arquivo : str
23
+ Nome do arquivo (sem extensao).
24
+
25
+ Returns
26
+ -------
27
+ None
28
+
29
+ Examples
30
+ --------
31
+ >>> JSON_para_SHP("dados/", "vetores")
32
+ """
33
+ # Load the JSON file
34
+ with open(fr'{caminho}/{arquivo}.json') as f:
35
+ data = json.load(f)
36
+
37
+ # Convert the JSON data to a GeoDataFrame
38
+ geoms = [shape(item['geometry']) for item in data['features']]
39
+ gdf = gpd.GeoDataFrame(data['features'], geometry=geoms, crs="EPSG:4326")
40
+
41
+ # Save the GeoDataFrame as a shapefile
42
+ gdf.to_file(fr'{caminho}/{arquivo}')
infrapy/geo.py ADDED
@@ -0,0 +1,569 @@
1
+ """geo.py - geoprocessamento: leitura de shapes, conversao de coordenadas,
2
+ calculo de km em alinhamentos/SNV, cortes e utilitarios geometricos."""
3
+
4
+ if True: # Bibliotecas
5
+ from typing import Tuple
6
+
7
+ import math
8
+ import warnings
9
+
10
+ import geopandas as gpd
11
+ import pandas as pd
12
+
13
+ import shapely
14
+ from shapely import ops
15
+ from shapely.ops import substring
16
+ from shapely.geometry import Point, LineString
17
+ import shapely.geometry as geom
18
+ from shapely.errors import ShapelyDeprecationWarning
19
+
20
+ import re
21
+
22
+ from . import config
23
+
24
+ if True: # Funcoes
25
+
26
+ # ------ Auxiliares -----
27
+ def closest_geometry(gdf: gpd.GeoDataFrame, point: shapely.geometry.point.Point | tuple) -> str:
28
+ """
29
+ Retorna a geometria mais próxima de um ponto.
30
+
31
+ Parameters
32
+ ----------
33
+ gdf : geopandas.GeoDataFrame
34
+ GeoDataFrame contendo uma coluna de geometria.
35
+ point : shapely.geometry.point.Point | tuple[float, float]
36
+ Ponto de referência ou tupla (x, y).
37
+
38
+ Returns
39
+ -------
40
+ tuple[int, BaseGeometry, float]
41
+ Índice da geometria, geometria encontrada e distância.
42
+
43
+ Raises
44
+ ------
45
+ RuntimeError
46
+ Se o geodataframe utilizado está vazio.
47
+
48
+ Examples
49
+ --------
50
+ >>> closest_geometry(gdf, Point(x, y))
51
+ >>> closest_geometry(gdf, (x, y))
52
+ """
53
+
54
+ if gdf.empty:
55
+ raise ValueError("GeoDataFrame is Empty.")
56
+
57
+ index_found = 0
58
+ smallest_dist = float("inf")
59
+
60
+
61
+ for index, geom in gdf.geometry.items():
62
+ dist = geom.distance(point)
63
+
64
+ if dist < smallest_dist:
65
+ smallest_dist = dist
66
+ index_found = index
67
+ geometry_found = geom
68
+
69
+ return index_found, geometry_found
70
+
71
+ def linestring_ini_fim_coordinates(line: LineString) -> tuple[float, float, float, float]:
72
+ """
73
+ Extrai as coordenadas inicial e final de uma linha em Lat/Long.
74
+
75
+ Parameters
76
+ ----------
77
+ line : shapely.geometry.LineString
78
+ Alinhamento do qual extrair os extremos.
79
+
80
+ Returns
81
+ -------
82
+ tuple of float
83
+ (start_lat, start_lon, end_lat, end_lon).
84
+ """
85
+
86
+ start_point = Point(line.coords[0])
87
+ start_lat = start_point.y
88
+ start_lon = start_point.x
89
+
90
+ end_point = Point(line.coords[-1])
91
+ end_lat = end_point.y
92
+ end_lon = end_point.x
93
+
94
+ return start_lat, start_lon, end_lat, end_lon
95
+
96
+ # ----- Bases -----
97
+ def EPSG_SIRGAS(zona: int) -> str | None:
98
+ """
99
+ Retorna o codigo EPSG (SIRGAS 2000 / UTM) correspondente a uma zona.
100
+
101
+ Parameters
102
+ ----------
103
+ zona : int
104
+ Zona UTM (17 a 25) ou 84 para WGS84 geografico.
105
+
106
+ Returns
107
+ -------
108
+ str or None
109
+ Codigo EPSG no formato "EPSG:XXXXX", ou None se a zona nao for
110
+ reconhecida.
111
+
112
+ Examples
113
+ --------
114
+ >>> EPSG_SIRGAS(23)
115
+ 'EPSG:31983'
116
+ """
117
+ tabela = {
118
+ 17: "EPSG:31977",
119
+ 18: "EPSG:31978",
120
+ 19: "EPSG:31979",
121
+ 20: "EPSG:31980",
122
+ 21: "EPSG:31981",
123
+ 22: "EPSG:31982",
124
+ 23: "EPSG:31983",
125
+ 24: "EPSG:31984",
126
+ 25: "EPSG:31985",
127
+ 84: "EPSG:4326",
128
+ }
129
+ return tabela.get(zona)
130
+
131
+ def EPSG_SAD(zona: int) -> str | None:
132
+ """
133
+ Retorna o codigo EPSG (SAD69 / UTM) correspondente a uma zona.
134
+
135
+ Parameters
136
+ ----------
137
+ zona : int
138
+ Zona UTM (17 a 25) ou 84 para WGS84 geografico.
139
+
140
+ Returns
141
+ -------
142
+ str or None
143
+ Codigo EPSG no formato "EPSG:XXXXX", ou None se a zona nao for
144
+ reconhecida.
145
+
146
+ Examples
147
+ --------
148
+ >>> EPSG_SAD(23)
149
+ 'EPSG:31983'
150
+ """
151
+ tabela = {
152
+ 17: "EPSG:29187",
153
+ 18: "EPSG:29188",
154
+ 19: "EPSG:29189",
155
+ 20: "EPSG:29190",
156
+ 21: "EPSG:29191",
157
+ 22: "EPSG:29192",
158
+ 23: "EPSG:29193",
159
+ 24: "EPSG:29194",
160
+ 25: "EPSG:29195",
161
+ 84: "EPSG:4326",
162
+ }
163
+
164
+ return tabela.get(zona)
165
+
166
+ def read_shape(caminho: str | None = None, epsg: str | None = None) -> gpd.GeoDataFrame:
167
+ """
168
+ Le um shapefile como GeoDataFrame.
169
+
170
+ Parameters
171
+ ----------
172
+ caminho : str, optional
173
+ Caminho do arquivo .shp. Se None, usa ALN_PADRAO do .env.
174
+ epsg : str, optional
175
+ CRS a atribuir caso o shapefile nao tenha um definido
176
+ (ex.: "EPSG:4326"). Padrao None.
177
+
178
+ Returns
179
+ -------
180
+ geopandas.GeoDataFrame
181
+ Os dados lidos.
182
+
183
+ Raises
184
+ ------
185
+ RuntimeError
186
+ Se caminho for None e ALN_PADRAO nao estiver definido no .env.
187
+
188
+ Examples
189
+ --------
190
+ >>> gdf = read_shape() # usa ALN_PADRAO do .env
191
+ >>> gdf = read_shape("aln.shp", "EPSG:4326")
192
+ """
193
+
194
+ #warnings.filterwarnings("ignore", category=ShapelyDeprecationWarning)
195
+
196
+ if caminho is None:
197
+ caminho = config.PATH_BASE_ALN()
198
+ if caminho is None:
199
+ raise RuntimeError("read_shape() sem caminho e sem ALN_PADRAO no .env.")
200
+
201
+ df = gpd.read_file(caminho)
202
+ if df.crs is None and epsg is None:
203
+ return df
204
+ elif df.crs is None and epsg is not None:
205
+ return gpd.GeoDataFrame(df, crs=epsg, geometry="geometry")
206
+ else:
207
+ return df
208
+
209
+ def load_SNV() -> gpd.GeoDataFrame:
210
+ """
211
+ Carrega e padroniza os SNVs (rede federal + cide) a partir dos
212
+ caminhos definidos no .env (SNV_FED e SNV_CIDE).
213
+
214
+ Junta as duas fontes, remove SNVs duplicados (preferindo a rede
215
+ federal), adiciona colunas de lat/long inicial e final e retorna
216
+ tudo em WGS84.
217
+
218
+ Returns
219
+ -------
220
+ geopandas.GeoDataFrame
221
+ SNVs padronizados em EPSG:4326.
222
+
223
+ Raises
224
+ ------
225
+ RuntimeError
226
+ Se SNV_FED ou SNV_CIDE nao estiverem definidos no .env.
227
+
228
+ Examples
229
+ --------
230
+ >>> snv = load_SNV()
231
+
232
+ """
233
+ fed = config.caminho_snv_fed()
234
+ cide = config.caminho_snv_cide()
235
+ if not fed or not cide:
236
+ raise RuntimeError("load_SNV() requer SNV_FED e SNV_CIDE no .env.")
237
+
238
+ VGEO_fed_df = gpd.read_file(fed)
239
+ VGEO_std_df = gpd.read_file(cide)
240
+
241
+ filt_fed = ['Codigo_SNV','Codigo_BR','Unidade_Fe','Local_Inic','Quilometra','Local_Fim','Quilometr0','Extensao','Superficie','versao_snv','geometry']
242
+ filt_std = ['Codigo_SNV','Codigo_Rod','Unidade_Fe','Local_Inic','Quilometra','Local_Fim','Quilometr0','Extensao','Superficie','Referencia','geometry']
243
+ col_padrao = ['SNV','ROD','UF','inicio','km i','fim','km f','ext','perfil','ref','geometry']
244
+
245
+ VGEO_fed_df = VGEO_fed_df[filt_fed]
246
+ VGEO_fed_df.columns = col_padrao
247
+ VGEO_fed_df.insert(7, 'fonte', 'vgeo_snv')
248
+
249
+ VGEO_std_df = VGEO_std_df[filt_std]
250
+ VGEO_std_df.columns = col_padrao
251
+ VGEO_std_df.insert(7, 'fonte', 'vgeo_cide')
252
+
253
+ VGEO_fed_df = VGEO_fed_df.to_crs('EPSG:4326')
254
+ VGEO_std_df = VGEO_std_df.to_crs('EPSG:4326')
255
+
256
+ vgeo_df = pd.concat([VGEO_fed_df, VGEO_std_df])
257
+ vgeo_df = vgeo_df.drop_duplicates(subset='SNV', keep='first').reset_index(drop=True)
258
+ vgeo_df[['lat i', 'long i', 'lat f', 'long f']] = vgeo_df['geometry'].apply(
259
+ lambda x: pd.Series(linestring_ini_fim_coordinates(x)))
260
+ geometry = vgeo_df.pop('geometry')
261
+ vgeo_df.insert(len(vgeo_df.columns), 'geometry', geometry)
262
+ return vgeo_df.to_crs('EPSG:4326')
263
+
264
+ # ------ Aligments -----
265
+ def point_to_km(gdf: gpd.GeoDataFrame, point, rod=None):
266
+ """
267
+ Retorna o km ate um ponto no alinhamento mais proximo (via UTM).
268
+
269
+ Parameters
270
+ ----------
271
+ aln_gdf : geopandas.GeoDataFrame
272
+ Alinhamentos, com colunas ZONA, KM I, NOME e geometry.
273
+ ponto : shapely.geometry.Point or tuple
274
+ Ponto de referencia (lon, lat) ou Point.
275
+
276
+ Returns
277
+ -------
278
+ tuple
279
+ (km, nome_do_alinhamento). Retorna ('Erro', ...) quando o ponto
280
+ esta distante (>300 m) ou em zona UTM incompativel.
281
+ """
282
+
283
+ if type(point) != shapely.geometry.point.Point:
284
+ Point(point)
285
+
286
+ closest_index, closest_aln = closest_geometry(gdf, point)
287
+
288
+ ALN = gdf.loc[closest_index,'ALN']
289
+
290
+ # alinhamento final
291
+ EPSG = gdf.loc[closest_index,'EPSG']
292
+
293
+ gdf_utm = gdf.to_crs(EPSG)
294
+ aln = gdf_utm.loc[closest_index,'geometry']
295
+
296
+ # calcular km
297
+ (lon,lat) = point.coords[0]
298
+
299
+ try:
300
+ point_gdf = gpd.GeoDataFrame(geometry=[point], crs='EPSG:4326')
301
+ point_gdf_utm = point_gdf.to_crs(EPSG)
302
+ utm_point = point_gdf_utm.loc[0,'geometry']
303
+ km = aln.project(utm_point)/1000 + gdf.loc[closest_index, 'km_i']# m
304
+ KM = float(round(km,3))
305
+
306
+ except:
307
+ km = "Erro"
308
+ ALN = "Erro"
309
+
310
+ return (KM,ALN)
311
+
312
+ def km_to_point(aln_gdf: gpd.GeoDataFrame, km: float, rod: str) -> tuple:
313
+ """
314
+ Dado um km e uma rodovia, retorna as coordenadas (lat, long) do ponto.
315
+
316
+ Parameters
317
+ ----------
318
+ aln_gdf : geopandas.GeoDataFrame
319
+ Alinhamentos, com colunas ROD, km_i, km_f, EPSG e geometry.
320
+ km : float
321
+ Quilometragem desejada.
322
+ rod : str
323
+ Rodovia a filtrar.
324
+
325
+ Returns
326
+ -------
327
+ tuple of float or None
328
+ (lat, long) do ponto, ou None se o km/rodovia nao resolver para
329
+ um unico alinhamento.
330
+ """
331
+ # Filtrar dataframe para rodovia desejada
332
+ gdf = aln_gdf[aln_gdf.ROD == rod]
333
+
334
+ # Encontrar SNV desejado
335
+ gdf = gdf[(gdf.km_i <= km)&(gdf.km_f > km)]
336
+
337
+ resultados_lat = []
338
+ resultados_long = []
339
+ resultados_aln = []
340
+
341
+ if (len(gdf) == 0):
342
+ return "Erro, não achamos este KM nesta rodovia"
343
+
344
+ elif len(gdf) != 1:
345
+ for index, row in gdf.iterrows():
346
+ km_i = row['km_i']
347
+ line = row['geometry']
348
+ epsg = row['EPSG']
349
+ aln = row["ALN"]
350
+ # Cálculo
351
+ line_df = gpd.GeoDataFrame(geometry=[line], crs="EPSG:4326")
352
+ line_df = line_df.to_crs(epsg)
353
+ line_utm = line_df.loc[0, 'geometry']
354
+
355
+ ponto = line_utm.interpolate((km - km_i) * 1000)
356
+ ponto_df = gpd.GeoDataFrame(geometry=[ponto], crs=epsg)
357
+ ponto_df = ponto_df.to_crs("EPSG:4326")
358
+ long, lat = ponto_df.loc[0, 'geometry'].coords[0]
359
+
360
+ resultados_lat.append(str(round(lat, 8)).replace('.', ','))
361
+ resultados_long.append(str(round(long, 8)).replace('.', ','))
362
+ resultados_aln.append(aln)
363
+
364
+ return ', '.join(resultados_lat), ', '.join(resultados_long), ', '.join(resultados_aln)
365
+
366
+ else:
367
+ # Entradas
368
+ aln_id = int(gdf.index[0])
369
+ km_i = gdf.loc[aln_id,'km_i']
370
+ line = gdf.loc[aln_id,'geometry']
371
+ epsg = gdf.loc[aln_id,'EPSG']
372
+ aln = gdf.loc[aln_id, "ALN"]
373
+
374
+ # Cálculo
375
+ line_df = gpd.GeoDataFrame(geometry=[line],crs="EPSG:4326")
376
+ line_df = line_df.to_crs(epsg)
377
+ line_utm = line_df.loc[0,'geometry']
378
+
379
+ ponto = line_utm.interpolate((km-km_i)*1000)
380
+ ponto_df = gpd.GeoDataFrame(geometry=[ponto],crs = epsg)
381
+ ponto_df = ponto_df.to_crs("EPSG:4326")
382
+ long,lat = ponto_df.loc[0,'geometry'].coords[0]
383
+
384
+ # Retornar coordenadas
385
+ return round(lat,8),round(long,8), aln
386
+
387
+ # ----- SNVs ------
388
+ def km_snv(snv_gdf: gpd.GeoDataFrame, ponto):
389
+ """
390
+ Dado um ponto e SNVs, retorna o km ate o ponto e o SNV mais proximo.
391
+
392
+ Parameters
393
+ ----------
394
+ snv_gdf : geopandas.GeoDataFrame
395
+ SNVs, com colunas 'km i', 'ext', 'SNV' e geometry.
396
+ ponto : shapely.geometry.Point or tuple
397
+ Ponto de referencia (lon, lat) ou Point.
398
+
399
+ Returns
400
+ -------
401
+ tuple
402
+ (km, codigo_SNV).
403
+ """
404
+
405
+ #Transformar coordenadas para ponto
406
+ if type(ponto) != shapely.geometry.point.Point:
407
+ Point(ponto)
408
+
409
+ # Correr a lista de SNVs e determinar qual o mais próximo do ponto desejado
410
+ snv_gdf_id = closest_geometry(snv_gdf, ponto)
411
+
412
+ snv_gdf_id_geometry = snv_gdf.iloc[snv_gdf_id]['geometry']
413
+
414
+ # Calcular a distância ao longo do SNV encontrado até o ponto
415
+ dist = snv_gdf_id_geometry.project(ponto) # Procura a distancia até o ponto
416
+ nearest_point = snv_gdf_id_geometry.interpolate(dist) # Gera um ponto mais procimo a distancia
417
+
418
+ # Dividir a extensão proporcionalmente e obter a quilometragem (sem cálculo direto)
419
+ propini = snv_gdf_id_geometry.project(nearest_point, normalized=True) # Pega a proporção da distancia do snv_gdf até o ponto mais proximo
420
+ km = snv_gdf.loc[snv_gdf_id, 'km i']+(snv_gdf.loc[snv_gdf_id, 'ext']*propini) # Acha o KM com base na tabela multiplicado pela proporção encontrada até o ponto
421
+
422
+ return km,snv_gdf.loc[snv_gdf_id]['SNV']
423
+
424
+ def km_snv2(snv_gdf: gpd.GeoDataFrame, ponto, data: pd.DataFrame | None = None):
425
+ """
426
+ Variante de ``km_snv`` que aceita um DataFrame auxiliar com nomes de
427
+ SNVs.
428
+
429
+ Parameters
430
+ ----------
431
+ snv_gdf : geopandas.GeoDataFrame
432
+ SNVs, com colunas 'km i', 'ext', 'SNV' e geometry.
433
+ ponto : shapely.geometry.Point or tuple
434
+ Ponto de referencia (lon, lat) ou Point.
435
+ data : pandas.DataFrame, optional
436
+ Se informado, deve conter as colunas 'longitude', 'latitude' e
437
+ 'name' para localizar o SNV pelo nome. Padrao None.
438
+
439
+ Returns
440
+ -------
441
+ tuple
442
+ (km, codigo_SNV).
443
+
444
+ """
445
+ # Caso haja algum banco de dados que tenha o nome dos SNVs:
446
+ if data is not None:
447
+ for index2,linha in data.iterrows():
448
+ ponto = (linha['longitude'],linha['latitude'])
449
+ name = data.iloc[index2]["name"]
450
+ snv_gdf_id = snv_gdf.index[snv_gdf["SNV"] == name][0]
451
+ snv_gdf_id_geometry = snv_gdf.iloc[snv_gdf_id]['geometry']
452
+
453
+ # Calcular a distância ao longo do SNV encontrado até o ponto
454
+ dist = snv_gdf_id_geometry.project(ponto) # Procura a distancia até o ponto
455
+ nearest_point = snv_gdf_id_geometry.interpolate(dist) # Gera um ponto mais proximo a distancia
456
+
457
+ # Dividir a extensão proporcionalmente e obter a quilometragem (sem cálculo direto)
458
+ propini = snv_gdf_id_geometry.project(nearest_point, normalized=True) # Pega a proporção da distancia do snv_gdf até o ponto mais proximo
459
+ km = snv_gdf.loc[snv_gdf_id, 'km i']+(snv_gdf.loc[snv_gdf_id, 'ext']*propini) # Acha o KM com base na tabela multiplicado pela proporção encontrada até o ponto
460
+
461
+ # Caso contrario:
462
+ else:
463
+ #Transformar coordenadas para ponto
464
+ if type(ponto) != shapely.geometry.point.Point:
465
+ ponto = Point(ponto)
466
+
467
+
468
+ # Correr a lista de SNVs e determinar qual o mais próximo do ponto desejado
469
+ snv_gdf_id = closest_geometry(snv_gdf, ponto)
470
+
471
+ snv_gdf_id_geometry = snv_gdf.iloc[snv_gdf_id]['geometry']
472
+
473
+ # Calcular a distância ao longo do SNV encontrado até o ponto
474
+ dist = snv_gdf_id_geometry.project(ponto) # Procura a distancia até o ponto
475
+ nearest_point = snv_gdf_id_geometry.interpolate(dist) # Gera um ponto mais proximo a distancia
476
+
477
+ # Dividir a extensão proporcionalmente e obter a quilometragem (sem cálculo direto)
478
+ propini = snv_gdf_id_geometry.project(nearest_point, normalized=True) # Pega a proporção da distancia do snv_gdf até o ponto mais proximo
479
+ km = snv_gdf.loc[snv_gdf_id, 'km i']+(snv_gdf.loc[snv_gdf_id, 'ext']*propini) # Acha o KM com base na tabela multiplicado pela proporção encontrada até o ponto
480
+
481
+ return km,snv_gdf.loc[snv_gdf_id]['SNV']
482
+
483
+ # ----- Shapely ------
484
+ def cut_linestring(km: float, line: LineString) -> tuple[LineString, LineString]:
485
+ """
486
+ Corta um alinhamento em um km especifico, gerando duas linhas.
487
+
488
+ Parameters
489
+ ----------
490
+ km : float
491
+ Quilometragem onde cortar (a partir do inicio da linha).
492
+ line : shapely.geometry.LineString
493
+ Alinhamento a cortar.
494
+
495
+ Returns
496
+ -------
497
+ tuple of shapely.geometry.LineString
498
+ (lin1, lin2), onde lin1 + lin2 reconstroem a linha original e o
499
+ ponto de divisao e o km informado.
500
+ """
501
+ comprimento = line.length
502
+ m = km * 1000
503
+ corte = line.interpolate(m)
504
+ projecao = line.project(corte)
505
+
506
+ lin1 = substring(line, 0, projecao)
507
+ lin2 = substring(line, projecao, comprimento)
508
+
509
+ return lin1, lin2 # onde lin1+lin2 é line, e o ponto de divisão é o km informado
510
+
511
+ def cut_linestring_with_points(aln_gdf: gpd.GeoDataFrame, rod, ponto_a, ponto_b):
512
+ """
513
+ Recorta um alinhamento entre dois pontos.
514
+
515
+ Converte cada ponto no km correspondente e delega o corte para
516
+ ``cortar``.
517
+
518
+ Parameters
519
+ ----------
520
+ aln_gdf : geopandas.GeoDataFrame
521
+ Alinhamentos, com coluna ROD e geometry.
522
+ rod : str
523
+ Rodovia a filtrar.
524
+ ponto_a : shapely.geometry.Point or tuple
525
+ Ponto inicial do corte.
526
+ ponto_b : shapely.geometry.Point or tuple
527
+ Ponto final do corte.
528
+
529
+ Returns
530
+ -------
531
+ shapely.geometry.LineString
532
+ Trecho recortado entre os dois pontos.
533
+ """
534
+ # Filtrar dataframe para rodovia desejada (ordenando para o caso de não estar ordenado)
535
+ line_gdf = aln_gdf[aln_gdf.ROD == rod]
536
+
537
+ # Ajustar ordem (possível apenas por ser a mesma rodovia)
538
+ km_a = point_to_km(line_gdf, ponto_a, rod)[0][1]
539
+ km_b = point_to_km(line_gdf, ponto_b, rod)[0][1]
540
+
541
+ # Gerar resultado
542
+ return cut_linestring(line_gdf, rod, km_a, km_b)
543
+
544
+ # ----- Randoms -----
545
+ def gms2gd(s: str) -> float:
546
+ """
547
+ Converte coordenada de graus-minutos-segundos para graus decimais.
548
+
549
+ Parameters
550
+ ----------
551
+ s : str
552
+ Coordenada em GMS, ex.: ``"0°51\'56.29\"S"``. Aceita virgula
553
+ como separador decimal.
554
+
555
+ Returns
556
+ -------
557
+ float
558
+ Valor em graus decimais (negativo para direcoes S, W ou O).
559
+ """
560
+ s = s.replace(" ","")
561
+
562
+ if "," in s:
563
+ s = s.replace(",",".")
564
+ # example: s = "0°51'56.29"S"
565
+ degrees, minutes, seconds, direction = re.split('[°\'"]+', s)
566
+ dd = float(degrees) + float(minutes)/60 + float(seconds)/(60*60);
567
+ if direction in ('S','W', "O"):
568
+ dd *= -1
569
+ return dd
infrapy/interface.py ADDED
@@ -0,0 +1,30 @@
1
+ """interface.py - Interfaces gráficas."""
2
+
3
+ from pathlib import Path
4
+ import tkinter as tk
5
+ from tkinter import filedialog
6
+
7
+
8
+ def tkinter_select_file() -> str | None:
9
+ """
10
+ Abre uma janela para seleção de um arquivo.
11
+
12
+ Returns
13
+ -------
14
+ str | None
15
+ Caminho do arquivo selecionado. Retorna ``None`` caso o usuário
16
+ cancele a seleção.
17
+ """
18
+
19
+ root = tk.Tk()
20
+ root.withdraw()
21
+ root.attributes("-topmost", True)
22
+
23
+ file_path = filedialog.askopenfilename()
24
+
25
+ root.destroy()
26
+
27
+ if not file_path:
28
+ raise "Usuário não escolheu um arquivo."
29
+
30
+ return file_path
infrapy/mapas.py ADDED
@@ -0,0 +1,85 @@
1
+ """mapas.py - geracao de mapas interativos com Folium."""
2
+
3
+ if True: # Bibliotecas
4
+ import json
5
+
6
+ import geopandas as gpd
7
+ import folium
8
+
9
+ from .cores import cor_aleatoria, cor_aleatoria_variada, cor_aleatoria_predef
10
+
11
+ if True: # Funcoes
12
+
13
+ def mapa_gdf(*gdfs: gpd.GeoDataFrame) -> folium.Map:
14
+ """
15
+ Gera um mapa Folium a partir de um ou mais GeoDataFrames.
16
+
17
+ Cada GeoDataFrame e desenhado com uma cor aleatoria e tooltips
18
+ contendo todas as colunas (exceto geometry). O mapa usa imagem de
19
+ satelite (Esri World Imagery) como base.
20
+
21
+ Parameters
22
+ ----------
23
+ *gdfs : geopandas.GeoDataFrame
24
+ Um ou mais GeoDataFrames a plotar.
25
+
26
+ Returns
27
+ -------
28
+ folium.Map
29
+ Mapa pronto para exibicao no notebook.
30
+
31
+ Examples
32
+ --------
33
+ >>> mapa_gdf(aln_gdf)
34
+ >>> mapa_gdf(aln_gdf, snv_gdf)
35
+ """
36
+ map = folium.Map(location=[-15.83, -47.86], width='100%', height='100%', left='0%', top='0%', position='relative',
37
+ tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
38
+ attr='Esri', min_zoom=0, max_zoom=18, zoom_start=4, min_lat=-90, max_lat=90, min_lon=-180, max_lon=180,
39
+ max_bounds=False, control_scale=False, prefer_canvas=False, no_touch=False, disable_3d=False,
40
+ png_enabled=False, zoom_control=True)
41
+
42
+ for gdf in gdfs:
43
+ JSON = json.loads(gdf.to_json())
44
+ colunas = [coluna for coluna in gdf.columns if coluna != 'geometry']
45
+ folium.GeoJson(
46
+ JSON,
47
+ tooltip=folium.features.GeoJsonTooltip(fields=colunas, labels=True),
48
+ style_function=lambda x: {'color': cor_aleatoria_variada()}
49
+ ).add_to(map)
50
+
51
+ return map
52
+
53
+ def mapa_shapely(shapely_geometries: list, epsg) -> folium.Map:
54
+ """
55
+ Gera um mapa Folium a partir de uma lista de geometrias Shapely.
56
+
57
+ As geometrias sao convertidas para WGS84 e desenhadas com cores
58
+ aleatorias, cada uma com um popup identificando sua ordem.
59
+
60
+ Parameters
61
+ ----------
62
+ shapely_geometries : list of shapely geometry
63
+ Geometrias a plotar.
64
+ epsg : int or str
65
+ CRS de origem das geometrias (ex.: 4326 ou "EPSG:31983").
66
+
67
+ Returns
68
+ -------
69
+ folium.Map
70
+ Mapa pronto para exibicao no notebook.
71
+ """
72
+ gdf = gpd.GeoDataFrame(geometry=shapely_geometries, crs=epsg)
73
+ if gdf.crs.to_string() != 'EPSG:4326':
74
+ gdf = gdf.to_crs(epsg=4326)
75
+
76
+ m = folium.Map(location=[-15.83, -47.86], width='100%', height='100%', left='0%', top='0%', position='relative', tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Esri', min_zoom=0, max_zoom=18, zoom_start=4, min_lat=-90, max_lat=90, min_lon=-180, max_lon=180, max_bounds=False, control_scale=False, prefer_canvas=False, no_touch=False, disable_3d=False, png_enabled=False, zoom_control=True)
77
+
78
+ for idx, geometry in enumerate(gdf['geometry']):
79
+ cor = cor_aleatoria_variada()
80
+ nome_geometria = f'Geometria {idx + 1}'
81
+ popup = folium.Popup(nome_geometria)
82
+ style_function = lambda x, cor=cor: {'fillColor': cor, 'color': cor}
83
+ folium.GeoJson(geometry, style_function=style_function, popup=popup).add_to(m)
84
+
85
+ return m
infrapy/sharepoint.py ADDED
@@ -0,0 +1,76 @@
1
+ """sharepoint.py - integracao com SharePoint / Office365.
2
+
3
+ Credenciais NAO ficam no codigo: sao lidas do .env via infrapy.config.
4
+ """
5
+
6
+ if True: # Bibliotecas
7
+ import os
8
+ import tempfile
9
+
10
+ import xlwings as xw
11
+
12
+ from office365.runtime.auth.authentication_context import AuthenticationContext
13
+ from office365.runtime.auth.client_credential import ClientCredential
14
+ from office365.runtime.auth.user_credential import UserCredential
15
+ from office365.sharepoint.client_context import ClientContext
16
+ from office365.sharepoint.files.file import File
17
+ from office365.sharepoint.folders.folder import Folder
18
+ from office365.runtime.auth.token_response import TokenResponse
19
+ from office365.sharepoint.files.system_object_type import FileSystemObjectType
20
+ from office365.sharepoint.listitems.listitem import ListItem
21
+
22
+ import msal
23
+
24
+ from . import config
25
+
26
+ if True: # Funcoes
27
+
28
+ def _contexto() -> ClientContext:
29
+ """
30
+ Cria um ClientContext autenticado com as credenciais do .env.
31
+
32
+ Returns
33
+ -------
34
+ office365.sharepoint.client_context.ClientContext
35
+ Contexto pronto para consultas ao SharePoint.
36
+ """
37
+ site = config.SHAREPOINT_SITE()
38
+ cert = config.SHAREPOINT_CERTIFICATE()
39
+
40
+ return ClientContext(site).with_client_certificate(**cert)
41
+
42
+ def buscar_arquivos_recursivamente(ctx: ClientContext, directory_url: str):
43
+ """
44
+ Gera (yield) todos os arquivos de uma pasta e suas subpastas.
45
+
46
+ Parameters
47
+ ----------
48
+ ctx : office365.sharepoint.client_context.ClientContext
49
+ Contexto autenticado do SharePoint.
50
+ directory_url : str
51
+ URL relativa da pasta raiz da busca.
52
+
53
+ Yields
54
+ ------
55
+ office365.sharepoint.files.file.File
56
+ Cada arquivo encontrado, recursivamente.
57
+ """
58
+ folder = ctx.web.get_folder_by_server_relative_url(directory_url)
59
+ ctx.load(folder)
60
+ ctx.execute_query()
61
+
62
+ # Buscar arquivos na pasta atual
63
+ files = folder.files
64
+ ctx.load(files)
65
+ ctx.execute_query()
66
+
67
+ for file in files:
68
+ yield file
69
+
70
+ # Buscar arquivos nas subpastas
71
+ folders = folder.folders
72
+ ctx.load(folders)
73
+ ctx.execute_query()
74
+
75
+ for subfolder in folders:
76
+ yield from buscar_arquivos_recursivamente(ctx, subfolder.properties['ServerRelativeUrl'])
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: infrapy
3
+ Version: 0.1.0
4
+ Summary: Biblioteca de funcoes de engenharia civil / rodovias (SNV, alinhamentos, geoprocessamento, Excel, SharePoint)
5
+ Author: Eduardo Abel
6
+ Keywords: engenharia civil,excel,geoprocessamento,rodovias,sharepoint
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: easygui
9
+ Requires-Dist: folium
10
+ Requires-Dist: geopandas
11
+ Requires-Dist: ipython
12
+ Requires-Dist: mapclassify
13
+ Requires-Dist: matplotlib
14
+ Requires-Dist: numpy
15
+ Requires-Dist: office365-rest-python-client
16
+ Requires-Dist: openpyxl
17
+ Requires-Dist: pandas
18
+ Requires-Dist: pyproj
19
+ Requires-Dist: python-dotenv
20
+ Requires-Dist: requests
21
+ Requires-Dist: rtree
22
+ Requires-Dist: scipy
23
+ Requires-Dist: shapely
24
+ Requires-Dist: simplekml
25
+ Requires-Dist: utm
26
+ Requires-Dist: xlsxwriter
27
+ Requires-Dist: xlwings
28
+ Requires-Dist: xyzservices
29
+ Description-Content-Type: text/markdown
30
+
31
+ # InfraPy
32
+
33
+ Biblioteca de funcoes de engenharia / rodovias: leitura de shapefiles,
34
+ calculo de km em alinhamentos e SNV, cortes, leitura de Excel (xlwings),
35
+ geracao de mapas Folium, integracao com SharePoint.
36
+
37
+ ## Instalacao (na sua maquina, modo editavel)
38
+
39
+ Dentro da pasta do projeto (onde esta o `pyproject.toml`):
40
+
41
+ ```bash
42
+ pip install -e .
43
+ ```
44
+
45
+ O `-e` aponta para o codigo-fonte: qualquer alteracao em `src/infrapy/*.py`
46
+ vale na hora (basta reiniciar o kernel), sem reinstalar.
47
+
48
+ ## Instalacao em outra maquina (via GitHub)
49
+
50
+ ```bash
51
+ pip install git+https://github.com/SrBlacky/InfraPy.git
52
+ ```
53
+
54
+ ## Configuracao (.env)
55
+
56
+ Segredos e caminhos pessoais NAO ficam no codigo. Eles vem de um arquivo
57
+ `.env` fora do repositorio.
58
+
59
+ 1. Copie `.env.example` para um `.env` numa pasta segura (fora do OneDrive,
60
+ ex.: `C:\Users\SEU_USUARIO\.infrapy\.env`).
61
+ 2. Preencha os valores.
62
+ 3. Ajuste o caminho do `.env` em `src/infrapy/__init__.py` (variavel
63
+ `_ENV_PATH`) OU defina a variavel de ambiente do Windows `INFRAPY_ENV`
64
+ apontando para o arquivo.
65
+
66
+ ## Uso
67
+
68
+ ```python
69
+ from infrapy import read_shape, ler_excel, ponto_para_km
70
+
71
+ aln = read_shape() # usa ALN_PADRAO do .env
72
+ ws, df = ler_excel("dados.xlsx", "Plan1", "A1")
73
+ km = ponto_para_km(aln, (-47.9, -15.8))
74
+ ```
75
+
76
+ Ou por modulo:
77
+
78
+ ```python
79
+ from infrapy.geo import cortar, km_para_ponto
80
+ from infrapy.sharepoint import abrir_mef
81
+ ```
82
+
83
+ ## Estrutura
84
+
85
+ ```
86
+ InfraPy/
87
+ ├── pyproject.toml identidade e dependencias do pacote
88
+ ├── .env.example modelo de configuracao (versionavel)
89
+ ├── .gitignore
90
+ └── src/
91
+ └── infrapy/
92
+ ├── __init__.py carrega o .env e expoe atalhos
93
+ ├── config.py le variaveis do ambiente
94
+ ├── geo.py geoprocessamento (o grosso das funcoes)
95
+ ├── excel.py leitura/escrita de Excel
96
+ ├── export.py exportar shp / json->shp
97
+ ├── mapas.py mapas Folium
98
+ ├── cores.py cores aleatorias
99
+ └── sharepoint.py integracao SharePoint
100
+ ```
@@ -0,0 +1,12 @@
1
+ infrapy/__init__.py,sha256=oABTbhSRLkGnlND4R7ICMHma1fabaVuU8qO93l9los4,229
2
+ infrapy/config.py,sha256=MNGQwJLnfvFeJw6C_QinlcaT8Idiw7MK8Wx5o295NXA,4592
3
+ infrapy/cores.py,sha256=KyaC2oRBLfGha03JOUQdqvWeJi2D0dqzlln3P2o6B04,4236
4
+ infrapy/excel.py,sha256=pdLxRsKM76apmLn6EA2VrRJddlMflbx5uvBgE497l3Q,4930
5
+ infrapy/export.py,sha256=4OPlVFfA_GZTNZeUuZSJguWhuEtiFafuO1dTLYbqIzI,1192
6
+ infrapy/geo.py,sha256=tQ_7U7mHTo7gIKOfzWS108FDiBq45nKMcmdf0YTv8Ks,20287
7
+ infrapy/interface.py,sha256=-xgh_K1-8d02cKevujKSTm98IZ74FjoWdFqOFMxZtFA,664
8
+ infrapy/mapas.py,sha256=3q1VEKNDVYfHwFIUqWK2PJcUvOSBcd-uS_q63EoGkuQ,3654
9
+ infrapy/sharepoint.py,sha256=U1nntfrp9wluHTc3cwUPpRXNloZXjVbbDo8Frw6gfec,2531
10
+ infrapy-0.1.0.dist-info/METADATA,sha256=Qqwnn_aQTgBwZG2pFW7RZEHRR7dzaiDSvdUfz2DCALU,2932
11
+ infrapy-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
12
+ infrapy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any