tr-imar 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.
- tr_imar/__init__.py +118 -0
- tr_imar/__main__.py +4 -0
- tr_imar/_version.py +1 -0
- tr_imar/cli.py +537 -0
- tr_imar/compliance.py +736 -0
- tr_imar/config.py +379 -0
- tr_imar/density.py +172 -0
- tr_imar/emsal.py +355 -0
- tr_imar/errors.py +22 -0
- tr_imar/geometry.py +627 -0
- tr_imar/height.py +182 -0
- tr_imar/parking.py +550 -0
- tr_imar/plan.py +132 -0
- tr_imar/py.typed +0 -0
- tr_imar/setbacks.py +214 -0
- tr_imar-0.1.0.dist-info/METADATA +327 -0
- tr_imar-0.1.0.dist-info/RECORD +21 -0
- tr_imar-0.1.0.dist-info/WHEEL +5 -0
- tr_imar-0.1.0.dist-info/entry_points.txt +2 -0
- tr_imar-0.1.0.dist-info/licenses/LICENSE +21 -0
- tr_imar-0.1.0.dist-info/top_level.txt +1 -0
tr_imar/__init__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""tr_imar: Planlı Alanlar İmar Yönetmeliği ve Otopark Yönetmeliği hesapları.
|
|
2
|
+
|
|
3
|
+
Turkish zoning (imar) calculations: TAKS/KAKS, emsal dışı areas and the 30 %
|
|
4
|
+
cap, floor count / building height, setbacks, buildable envelope of a parcel,
|
|
5
|
+
parking requirement and a compliance check with Turkish messages.
|
|
6
|
+
|
|
7
|
+
Hızlı başlangıç::
|
|
8
|
+
|
|
9
|
+
from tr_imar import PlanConditions, Project, check_compliance
|
|
10
|
+
|
|
11
|
+
plan = PlanConditions(taks=0.35, kaks=1.40, floors=4, nizam="ayrik")
|
|
12
|
+
proje = Project(parcel_area=1000, footprint=340, floor_area=1360, floors=4,
|
|
13
|
+
building_height=14.4, setbacks={"on": 5, "yan": 3, "arka": 3})
|
|
14
|
+
print(check_compliance(proje, plan).to_text())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from ._version import __version__
|
|
18
|
+
from .compliance import CheckItem, ComplianceReport, Project, check_compliance
|
|
19
|
+
from .config import FIELD_SOURCES, NATIONAL, FieldSource, ImarConfig, load_config
|
|
20
|
+
from .density import DensityResult, resolve_density
|
|
21
|
+
from .emsal import (
|
|
22
|
+
EMSAL_DISI_CATEGORIES,
|
|
23
|
+
EmsalDisiItem,
|
|
24
|
+
EmsalDisiLine,
|
|
25
|
+
EmsalDisiResult,
|
|
26
|
+
evaluate_emsal_disi,
|
|
27
|
+
)
|
|
28
|
+
from .errors import ImarConfigError, ImarError, ImarGeometryError, ImarInputError
|
|
29
|
+
from .geometry import (
|
|
30
|
+
Envelope,
|
|
31
|
+
RectangleEnvelope,
|
|
32
|
+
buildable_envelope,
|
|
33
|
+
classify_edges,
|
|
34
|
+
is_convex,
|
|
35
|
+
offset_polygon,
|
|
36
|
+
polygon_area,
|
|
37
|
+
rectangle_envelope,
|
|
38
|
+
)
|
|
39
|
+
from .height import (
|
|
40
|
+
classify_height,
|
|
41
|
+
floor_height_limits,
|
|
42
|
+
floors_by_road_width,
|
|
43
|
+
floors_for_setbacks,
|
|
44
|
+
max_building_height,
|
|
45
|
+
)
|
|
46
|
+
from .parking import (
|
|
47
|
+
DEFAULT_PARKING_TABLE,
|
|
48
|
+
ParkingDemand,
|
|
49
|
+
ParkingLine,
|
|
50
|
+
ParkingResult,
|
|
51
|
+
ParkingRule,
|
|
52
|
+
ParkingTable,
|
|
53
|
+
parking_requirement,
|
|
54
|
+
)
|
|
55
|
+
from .plan import NIZAM_TYPES, ZONE_TYPES, PlanConditions
|
|
56
|
+
from .setbacks import SetbackRequirement, required_setbacks, tower_spacing
|
|
57
|
+
|
|
58
|
+
# Türkçe takma adlar (isteğe bağlı)
|
|
59
|
+
uygunluk_kontrolu = check_compliance
|
|
60
|
+
bahce_mesafeleri = required_setbacks
|
|
61
|
+
yapilasma_zarfi = buildable_envelope
|
|
62
|
+
otopark_ihtiyaci = parking_requirement
|
|
63
|
+
yogunluk = resolve_density
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"DEFAULT_PARKING_TABLE",
|
|
67
|
+
"EMSAL_DISI_CATEGORIES",
|
|
68
|
+
"FIELD_SOURCES",
|
|
69
|
+
"NATIONAL",
|
|
70
|
+
"NIZAM_TYPES",
|
|
71
|
+
"ZONE_TYPES",
|
|
72
|
+
"CheckItem",
|
|
73
|
+
"ComplianceReport",
|
|
74
|
+
"DensityResult",
|
|
75
|
+
"EmsalDisiItem",
|
|
76
|
+
"EmsalDisiLine",
|
|
77
|
+
"EmsalDisiResult",
|
|
78
|
+
"Envelope",
|
|
79
|
+
"FieldSource",
|
|
80
|
+
"ImarConfig",
|
|
81
|
+
"ImarConfigError",
|
|
82
|
+
"ImarError",
|
|
83
|
+
"ImarGeometryError",
|
|
84
|
+
"ImarInputError",
|
|
85
|
+
"ParkingDemand",
|
|
86
|
+
"ParkingLine",
|
|
87
|
+
"ParkingResult",
|
|
88
|
+
"ParkingRule",
|
|
89
|
+
"ParkingTable",
|
|
90
|
+
"PlanConditions",
|
|
91
|
+
"Project",
|
|
92
|
+
"RectangleEnvelope",
|
|
93
|
+
"SetbackRequirement",
|
|
94
|
+
"__version__",
|
|
95
|
+
"bahce_mesafeleri",
|
|
96
|
+
"buildable_envelope",
|
|
97
|
+
"check_compliance",
|
|
98
|
+
"classify_edges",
|
|
99
|
+
"classify_height",
|
|
100
|
+
"evaluate_emsal_disi",
|
|
101
|
+
"floor_height_limits",
|
|
102
|
+
"floors_by_road_width",
|
|
103
|
+
"floors_for_setbacks",
|
|
104
|
+
"is_convex",
|
|
105
|
+
"load_config",
|
|
106
|
+
"max_building_height",
|
|
107
|
+
"offset_polygon",
|
|
108
|
+
"otopark_ihtiyaci",
|
|
109
|
+
"parking_requirement",
|
|
110
|
+
"polygon_area",
|
|
111
|
+
"rectangle_envelope",
|
|
112
|
+
"required_setbacks",
|
|
113
|
+
"resolve_density",
|
|
114
|
+
"tower_spacing",
|
|
115
|
+
"uygunluk_kontrolu",
|
|
116
|
+
"yapilasma_zarfi",
|
|
117
|
+
"yogunluk",
|
|
118
|
+
]
|
tr_imar/__main__.py
ADDED
tr_imar/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
tr_imar/cli.py
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""Komut satırı arayüzü: ``tr-imar``.
|
|
2
|
+
|
|
3
|
+
Alt komutlar: ``yogunluk``, ``cekme``, ``yukseklik``, ``zarf``, ``otopark``,
|
|
4
|
+
``kontrol``, ``yapilandirma``. Ayrıntı için ``tr-imar <komut> --help``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional, Sequence, TextIO
|
|
14
|
+
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
from .compliance import Project, check_compliance
|
|
17
|
+
from .config import FIELD_SOURCES, NATIONAL, ImarConfig, load_config
|
|
18
|
+
from .density import resolve_density
|
|
19
|
+
from .errors import ImarError
|
|
20
|
+
from .geometry import buildable_envelope, polygon_area, rectangle_envelope
|
|
21
|
+
from .height import (
|
|
22
|
+
classify_height,
|
|
23
|
+
floor_height_limits,
|
|
24
|
+
floors_by_road_width,
|
|
25
|
+
max_building_height,
|
|
26
|
+
)
|
|
27
|
+
from .parking import DEFAULT_PARKING_TABLE, ParkingDemand, ParkingTable, parking_requirement
|
|
28
|
+
from .plan import NIZAM_TYPES, ZONE_TYPES, PlanConditions
|
|
29
|
+
from .setbacks import required_setbacks
|
|
30
|
+
|
|
31
|
+
__all__ = ["build_parser", "main"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _num(x: Optional[float], unit: str = "", nd: int = 2) -> str:
|
|
35
|
+
if x is None:
|
|
36
|
+
return "-"
|
|
37
|
+
s = f"{x:,.{nd}f}".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
38
|
+
return f"{s} {unit}".rstrip()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _dump(obj: Any, out: TextIO) -> None:
|
|
42
|
+
out.write(json.dumps(obj, ensure_ascii=False, indent=2, default=str) + "\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _config(args: argparse.Namespace) -> ImarConfig:
|
|
46
|
+
return load_config(args.config) if getattr(args, "config", None) else NATIONAL
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _plan_from_args(args: argparse.Namespace) -> PlanConditions:
|
|
50
|
+
return PlanConditions(
|
|
51
|
+
taks=getattr(args, "taks", None),
|
|
52
|
+
kaks=getattr(args, "kaks", None),
|
|
53
|
+
floors=getattr(args, "kat", None),
|
|
54
|
+
nizam=getattr(args, "nizam", "ayrik") or "ayrik",
|
|
55
|
+
front_setback=getattr(args, "plan_on", None),
|
|
56
|
+
side_setback=getattr(args, "plan_yan", None),
|
|
57
|
+
rear_setback=getattr(args, "plan_arka", None),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Komutlar
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _cmd_yogunluk(args: argparse.Namespace, out: TextIO) -> int:
|
|
67
|
+
cfg = _config(args)
|
|
68
|
+
plan = _plan_from_args(args)
|
|
69
|
+
res = resolve_density(args.parsel, plan, envelope_area=args.zarf_alani, config=cfg)
|
|
70
|
+
if args.json:
|
|
71
|
+
_dump(res.to_dict(), out)
|
|
72
|
+
return 0
|
|
73
|
+
out.write(f"Net parsel alanı : {_num(res.parcel_area, 'm²')}\n")
|
|
74
|
+
out.write(
|
|
75
|
+
f"Azami taban alanı : {_num(res.max_footprint, 'm²')} (TAKS {res.taks:.4f}; "
|
|
76
|
+
f"{res.sources.get('taks', '')})\n"
|
|
77
|
+
)
|
|
78
|
+
out.write(
|
|
79
|
+
f"Azami emsal alanı : {_num(res.max_floor_area, 'm²')}"
|
|
80
|
+
+ (f" (KAKS {res.kaks:.4f}; {res.sources.get('kaks', '')})" if res.kaks else "")
|
|
81
|
+
+ "\n"
|
|
82
|
+
)
|
|
83
|
+
out.write(f"Emsal dışı %30 tavanı : {_num(res.emsal_disi_cap, 'm²')} (PAİY m.5/8)\n")
|
|
84
|
+
for n in res.notes:
|
|
85
|
+
out.write(f" * {n}\n")
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _cmd_cekme(args: argparse.Namespace, out: TextIO) -> int:
|
|
90
|
+
cfg = _config(args)
|
|
91
|
+
if args.kat is None and args.yukseklik is None:
|
|
92
|
+
raise ImarError("--kat veya --yukseklik verilmeli")
|
|
93
|
+
plan = PlanConditions(
|
|
94
|
+
nizam=args.nizam,
|
|
95
|
+
front_setback=args.plan_on,
|
|
96
|
+
side_setback=args.plan_yan,
|
|
97
|
+
rear_setback=args.plan_arka,
|
|
98
|
+
)
|
|
99
|
+
req = required_setbacks(
|
|
100
|
+
floors=args.kat,
|
|
101
|
+
height=args.yukseklik,
|
|
102
|
+
plan=plan,
|
|
103
|
+
floor_height=args.kat_yuksekligi,
|
|
104
|
+
config=cfg,
|
|
105
|
+
)
|
|
106
|
+
if args.json:
|
|
107
|
+
_dump(req.to_dict(), out)
|
|
108
|
+
return 0
|
|
109
|
+
out.write(f"Hesaba esas kat adedi : {req.floors_counted}\n")
|
|
110
|
+
out.write(f"Ön bahçe : {_num(req.front, 'm')} ({req.sources['front']})\n")
|
|
111
|
+
yan = "aranmaz (bitişik)" if req.attached_sides >= 2 else _num(req.side, "m")
|
|
112
|
+
out.write(f"Yan bahçe : {yan} ({req.sources['side']})\n")
|
|
113
|
+
out.write(f"Arka bahçe : {_num(req.rear, 'm')} ({req.sources['rear']})\n")
|
|
114
|
+
if req.side != req.side_park:
|
|
115
|
+
out.write(
|
|
116
|
+
f"Park alanına komşu yan/arka: {_num(req.side_park, 'm')} / "
|
|
117
|
+
f"{_num(req.rear_park, 'm')}\n"
|
|
118
|
+
)
|
|
119
|
+
for n in req.notes:
|
|
120
|
+
out.write(f" * {n}\n")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _cmd_yukseklik(args: argparse.Namespace, out: TextIO) -> int:
|
|
125
|
+
cfg = _config(args)
|
|
126
|
+
result: Dict[str, Any] = {}
|
|
127
|
+
if args.yol is not None:
|
|
128
|
+
result["yol_genisligi"] = args.yol
|
|
129
|
+
result["en_cok_kat"] = floors_by_road_width(args.yol, cfg)
|
|
130
|
+
floors = args.kat if args.kat is not None else result.get("en_cok_kat")
|
|
131
|
+
if floors is not None:
|
|
132
|
+
lim = floor_height_limits(
|
|
133
|
+
args.bolge, floors=floors, ground_mezzanine=args.asma_kat, config=cfg
|
|
134
|
+
)
|
|
135
|
+
result["kat"] = floors
|
|
136
|
+
result["kat_yukseklikleri"] = lim
|
|
137
|
+
result["hmax"] = max_building_height(
|
|
138
|
+
floors, args.bolge, ground_mezzanine=args.asma_kat, config=cfg
|
|
139
|
+
)
|
|
140
|
+
result["hmax_subasman_dahil"] = max_building_height(
|
|
141
|
+
floors,
|
|
142
|
+
args.bolge,
|
|
143
|
+
ground_mezzanine=args.asma_kat,
|
|
144
|
+
include_subasman=True,
|
|
145
|
+
subasman=args.subasman,
|
|
146
|
+
config=cfg,
|
|
147
|
+
)
|
|
148
|
+
if args.siniflandir is not None:
|
|
149
|
+
result["sinif"] = classify_height(args.siniflandir, args.yapi_yuksekligi, cfg)
|
|
150
|
+
if not result:
|
|
151
|
+
raise ImarError("--kat, --yol veya --siniflandir verilmeli")
|
|
152
|
+
if args.json:
|
|
153
|
+
_dump(result, out)
|
|
154
|
+
return 0
|
|
155
|
+
if "en_cok_kat" in result:
|
|
156
|
+
out.write(
|
|
157
|
+
f"Yol genişliği {_num(args.yol, 'm')} -> en çok {result['en_cok_kat']} kat "
|
|
158
|
+
"(bodrum hariç; PAİY m.9/1-a)\n"
|
|
159
|
+
)
|
|
160
|
+
if "hmax" in result:
|
|
161
|
+
lim = result["kat_yukseklikleri"]
|
|
162
|
+
out.write(
|
|
163
|
+
f"Bölge: {args.bolge}, {result['kat']} kat; azami kat yüksekliği zemin "
|
|
164
|
+
f"{_num(lim['zemin'], 'm')}, normal {_num(lim['normal'], 'm')} (PAİY m.28/1)\n"
|
|
165
|
+
)
|
|
166
|
+
out.write(f"Hmax = {_num(result['hmax'], 'm')} (PAİY m.28/2)\n")
|
|
167
|
+
out.write(f"Hmax (subasman dahil, m.9/1-c) = {_num(result['hmax_subasman_dahil'], 'm')}\n")
|
|
168
|
+
if "sinif" in result:
|
|
169
|
+
ad = {
|
|
170
|
+
"normal": "yüksek yapı değil",
|
|
171
|
+
"yuksek": "yüksek yapı",
|
|
172
|
+
"cok_yuksek": "çok yüksek yapı",
|
|
173
|
+
}[result["sinif"]]
|
|
174
|
+
out.write(f"Yükseklik sınıfı: {ad} (PAİY m.4/1-aaaaa)\n")
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _parse_polygon(text: str) -> List[List[float]]:
|
|
179
|
+
pts: List[List[float]] = []
|
|
180
|
+
for part in text.replace("\n", ";").split(";"):
|
|
181
|
+
part = part.strip()
|
|
182
|
+
if not part:
|
|
183
|
+
continue
|
|
184
|
+
xy = [float(v) for v in part.replace(" ", ",").split(",") if v]
|
|
185
|
+
if len(xy) != 2:
|
|
186
|
+
raise ImarError(f"Koordinat 'x,y' biçiminde olmalı: {part!r}")
|
|
187
|
+
pts.append(xy)
|
|
188
|
+
return pts
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _read_geojson_polygon(path: str) -> List[List[float]]:
|
|
192
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
193
|
+
if data.get("type") == "FeatureCollection":
|
|
194
|
+
if not data.get("features"):
|
|
195
|
+
raise ImarError("GeoJSON içinde detay (feature) yok")
|
|
196
|
+
data = data["features"][0]
|
|
197
|
+
geom = data.get("geometry", data)
|
|
198
|
+
if geom.get("type") == "Polygon":
|
|
199
|
+
return [list(map(float, c[:2])) for c in geom["coordinates"][0]]
|
|
200
|
+
if geom.get("type") == "MultiPolygon" and len(geom["coordinates"]) == 1:
|
|
201
|
+
return [list(map(float, c[:2])) for c in geom["coordinates"][0][0]]
|
|
202
|
+
raise ImarError("GeoJSON geometrisi tek parçalı Polygon olmalı")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _cmd_zarf(args: argparse.Namespace, out: TextIO) -> int:
|
|
206
|
+
cfg = _config(args)
|
|
207
|
+
if args.kat is None and args.yukseklik is None:
|
|
208
|
+
raise ImarError("--kat veya --yukseklik verilmeli")
|
|
209
|
+
plan = PlanConditions(
|
|
210
|
+
nizam=args.nizam,
|
|
211
|
+
front_setback=args.plan_on,
|
|
212
|
+
side_setback=args.plan_yan,
|
|
213
|
+
rear_setback=args.plan_arka,
|
|
214
|
+
)
|
|
215
|
+
req = required_setbacks(floors=args.kat, height=args.yukseklik, plan=plan, config=cfg)
|
|
216
|
+
if args.dikdortgen:
|
|
217
|
+
try:
|
|
218
|
+
w, d = (float(v) for v in args.dikdortgen.lower().split("x"))
|
|
219
|
+
except ValueError as exc:
|
|
220
|
+
raise ImarError("--dikdortgen CEPHExDERINLIK biçiminde olmalı, ör. 20x30") from exc
|
|
221
|
+
att = req.attached_sides
|
|
222
|
+
env = rectangle_envelope(
|
|
223
|
+
w,
|
|
224
|
+
d,
|
|
225
|
+
req.front,
|
|
226
|
+
0.0 if att >= 1 else req.side,
|
|
227
|
+
req.rear,
|
|
228
|
+
side2=0.0 if att >= 2 else req.side,
|
|
229
|
+
)
|
|
230
|
+
res = {
|
|
231
|
+
"parsel_alani": w * d,
|
|
232
|
+
"zarf_genislik": env.width,
|
|
233
|
+
"zarf_derinlik": env.depth,
|
|
234
|
+
"zarf_alani": env.area,
|
|
235
|
+
"on": req.front,
|
|
236
|
+
"yan": req.side,
|
|
237
|
+
"arka": req.rear,
|
|
238
|
+
}
|
|
239
|
+
if args.json:
|
|
240
|
+
_dump(res, out)
|
|
241
|
+
else:
|
|
242
|
+
out.write(f"Parsel {_num(w, 'm')} x {_num(d, 'm')} = {_num(w * d, 'm²')}\n")
|
|
243
|
+
out.write(
|
|
244
|
+
f"Çekmeler: ön {_num(req.front, 'm')}, yan {_num(req.side, 'm')}"
|
|
245
|
+
f"{' (bitişik cephe 0)' if att else ''}, arka {_num(req.rear, 'm')}\n"
|
|
246
|
+
)
|
|
247
|
+
out.write(
|
|
248
|
+
f"Zarf {_num(env.width, 'm')} x {_num(env.depth, 'm')} = {_num(env.area, 'm²')}\n"
|
|
249
|
+
)
|
|
250
|
+
return 0
|
|
251
|
+
if args.poligon:
|
|
252
|
+
coords = _parse_polygon(args.poligon)
|
|
253
|
+
elif args.geojson:
|
|
254
|
+
coords = _read_geojson_polygon(args.geojson)
|
|
255
|
+
else:
|
|
256
|
+
raise ImarError("--dikdortgen, --poligon veya --geojson verilmeli")
|
|
257
|
+
roles = [r.strip() for r in args.roller.split(",")] if args.roller else None
|
|
258
|
+
if roles is None and not args.on_kenar:
|
|
259
|
+
raise ImarError("Poligon için --on-kenar veya --roller verilmeli")
|
|
260
|
+
env_p = buildable_envelope(
|
|
261
|
+
coords, req, edge_roles=roles, front_edges=args.on_kenar or None, method=args.yontem
|
|
262
|
+
)
|
|
263
|
+
if args.cikti:
|
|
264
|
+
Path(args.cikti).write_text(
|
|
265
|
+
json.dumps(env_p.to_geojson(), ensure_ascii=False, indent=2), encoding="utf-8"
|
|
266
|
+
)
|
|
267
|
+
if args.json:
|
|
268
|
+
_dump(env_p.to_geojson(), out)
|
|
269
|
+
return 0
|
|
270
|
+
out.write(f"Parsel alanı : {_num(polygon_area(coords), 'm²')}\n")
|
|
271
|
+
for i, (r, dist) in enumerate(zip(env_p.edge_roles, env_p.edge_distances)):
|
|
272
|
+
out.write(f" kenar {i}: {r:<9} {_num(dist, 'm')}\n")
|
|
273
|
+
out.write(
|
|
274
|
+
f"Zarf alanı : {_num(env_p.area, 'm²')} ({env_p.method}, {len(env_p.parts)} parça)\n"
|
|
275
|
+
)
|
|
276
|
+
for part in env_p.parts:
|
|
277
|
+
out.write(" " + "; ".join(f"{x:.3f},{y:.3f}" for x, y in part) + "\n")
|
|
278
|
+
for n in env_p.notes:
|
|
279
|
+
out.write(f" * {n}\n")
|
|
280
|
+
if args.cikti:
|
|
281
|
+
out.write(f"GeoJSON yazıldı: {args.cikti}\n")
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _parking_table(args: argparse.Namespace) -> ParkingTable:
|
|
286
|
+
if getattr(args, "tablo", None):
|
|
287
|
+
return ParkingTable.from_json(args.tablo)
|
|
288
|
+
return DEFAULT_PARKING_TABLE
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _cmd_otopark(args: argparse.Namespace, out: TextIO) -> int:
|
|
292
|
+
table = _parking_table(args)
|
|
293
|
+
if args.liste:
|
|
294
|
+
if args.json:
|
|
295
|
+
_dump(table.to_dict(), out)
|
|
296
|
+
return 0
|
|
297
|
+
group = None
|
|
298
|
+
for key, rule in table.items():
|
|
299
|
+
if rule.group != group:
|
|
300
|
+
group = rule.group
|
|
301
|
+
out.write(f"{group}\n")
|
|
302
|
+
if rule.kind == "mesken":
|
|
303
|
+
desc = rule.note
|
|
304
|
+
elif rule.kind == "oran":
|
|
305
|
+
desc = f"{rule.unit} x {rule.per:g}"
|
|
306
|
+
elif rule.kind in ("adet",):
|
|
307
|
+
desc = f"{rule.per:g} {rule.unit} için 1"
|
|
308
|
+
elif rule.kind == "akaryakit":
|
|
309
|
+
desc = "pompa ve lift/kanal başına 1"
|
|
310
|
+
else:
|
|
311
|
+
desc = f"{rule.per:g} m² için 1"
|
|
312
|
+
if rule.min_one_per_unit:
|
|
313
|
+
desc += " (bağımsız bölüm başına en az 1)"
|
|
314
|
+
out.write(f" {key:<18} {rule.label}: {desc}\n")
|
|
315
|
+
out.write(f"Kaynak: {table.source}\n")
|
|
316
|
+
return 0
|
|
317
|
+
demands: List[ParkingDemand] = []
|
|
318
|
+
if args.girdi:
|
|
319
|
+
data = json.loads(Path(args.girdi).read_text(encoding="utf-8"))
|
|
320
|
+
if isinstance(data, dict):
|
|
321
|
+
data = data.get("otopark", data.get("parking", [data]))
|
|
322
|
+
demands.extend(ParkingDemand.from_dict(d) for d in data)
|
|
323
|
+
daireler: List[float] = []
|
|
324
|
+
for d in args.daire or []:
|
|
325
|
+
daireler.extend(float(v) for v in str(d).split(",") if v.strip())
|
|
326
|
+
if daireler:
|
|
327
|
+
demands.append(ParkingDemand("mesken", unit_areas=daireler))
|
|
328
|
+
for spec in args.kullanim or []:
|
|
329
|
+
if "=" not in spec:
|
|
330
|
+
raise ImarError(f"--kullanim ANAHTAR=MİKTAR biçiminde olmalı: {spec!r}")
|
|
331
|
+
key, val = spec.split("=", 1)
|
|
332
|
+
rule = table[key.strip()]
|
|
333
|
+
q = float(val)
|
|
334
|
+
if rule.kind in ("adet", "oran"):
|
|
335
|
+
demands.append(ParkingDemand(rule.key, count=q))
|
|
336
|
+
elif rule.kind == "akaryakit":
|
|
337
|
+
demands.append(ParkingDemand(rule.key, pumps=int(q)))
|
|
338
|
+
else:
|
|
339
|
+
demands.append(ParkingDemand(rule.key, area=q))
|
|
340
|
+
if not demands:
|
|
341
|
+
raise ImarError("En az bir kullanım verilmeli (--daire, --kullanim veya --girdi)")
|
|
342
|
+
res = parking_requirement(
|
|
343
|
+
demands, table=table, factor=args.katsayi, residential_reduction=args.azaltma
|
|
344
|
+
)
|
|
345
|
+
if args.json:
|
|
346
|
+
_dump(res.to_dict(), out)
|
|
347
|
+
return 0
|
|
348
|
+
for line in res.lines:
|
|
349
|
+
out.write(f"{line.label}: {line.basis} -> {line.spaces:.2f}\n")
|
|
350
|
+
out.write(f"Toplam (ham) : {res.raw_total:.2f}\n")
|
|
351
|
+
out.write(f"Zorunlu otopark: {res.total} adet\n")
|
|
352
|
+
for k, v in res.extras.items():
|
|
353
|
+
out.write(f"Ayrıca: {v} adet {k.replace('_', ' ')} yeri\n")
|
|
354
|
+
for n in res.notes:
|
|
355
|
+
out.write(f" * {n}\n")
|
|
356
|
+
return 0
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _cmd_kontrol(args: argparse.Namespace, out: TextIO) -> int:
|
|
360
|
+
cfg = _config(args)
|
|
361
|
+
try:
|
|
362
|
+
data = json.loads(Path(args.proje).read_text(encoding="utf-8"))
|
|
363
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
364
|
+
raise ImarError(f"Proje dosyası okunamadı: {args.proje} ({exc})") from exc
|
|
365
|
+
if not isinstance(data, dict):
|
|
366
|
+
raise ImarError("Proje dosyası bir JSON nesnesi olmalı")
|
|
367
|
+
plan = PlanConditions.from_dict(data.get("plan", {}))
|
|
368
|
+
proje_data = data.get("proje", data.get("project")) or {}
|
|
369
|
+
proje = Project.from_dict(proje_data)
|
|
370
|
+
if "yapilandirma" in data:
|
|
371
|
+
cfg = ImarConfig.from_dict(data["yapilandirma"], base=cfg)
|
|
372
|
+
table = _parking_table(args)
|
|
373
|
+
if "otopark_tablosu" in data:
|
|
374
|
+
table = table.with_overrides(data["otopark_tablosu"])
|
|
375
|
+
report = check_compliance(proje, plan, cfg, parking_table=table, envelope_method=args.yontem)
|
|
376
|
+
if args.json:
|
|
377
|
+
out.write(report.to_json() + "\n")
|
|
378
|
+
else:
|
|
379
|
+
out.write(report.to_text() + "\n")
|
|
380
|
+
return 0 if report.ok else 1
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _cmd_yapilandirma(args: argparse.Namespace, out: TextIO) -> int:
|
|
384
|
+
cfg = _config(args)
|
|
385
|
+
if args.kaynaklar:
|
|
386
|
+
rows = {
|
|
387
|
+
name: {
|
|
388
|
+
"deger": cfg.to_dict()[name],
|
|
389
|
+
"dayanak": fs.article,
|
|
390
|
+
"degistirilemez": fs.protected,
|
|
391
|
+
"aciklama": fs.description,
|
|
392
|
+
}
|
|
393
|
+
for name, fs in FIELD_SOURCES.items()
|
|
394
|
+
}
|
|
395
|
+
_dump(rows, out)
|
|
396
|
+
else:
|
|
397
|
+
_dump(cfg.to_dict(), out)
|
|
398
|
+
return 0
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ---------------------------------------------------------------------------
|
|
402
|
+
# Ayrıştırıcı
|
|
403
|
+
# ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
407
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
408
|
+
common.add_argument(
|
|
409
|
+
"--config",
|
|
410
|
+
"--yapilandirma",
|
|
411
|
+
dest="config",
|
|
412
|
+
metavar="JSON",
|
|
413
|
+
help="Ulusal varsayılanları değiştiren yerel yönetmelik JSON dosyası",
|
|
414
|
+
)
|
|
415
|
+
common.add_argument("--json", action="store_true", help="Çıktıyı JSON olarak yaz")
|
|
416
|
+
|
|
417
|
+
p = argparse.ArgumentParser(
|
|
418
|
+
prog="tr-imar",
|
|
419
|
+
description="Planlı Alanlar İmar Yönetmeliği ve Otopark Yönetmeliği hesapları "
|
|
420
|
+
"(TAKS/KAKS, emsal dışı, bahçe mesafeleri, yapılaşma zarfı, otopark, uyum kontrolü).",
|
|
421
|
+
epilog="Sonuçlar ön kontrol niteliğindedir; plan notları ve idarenin yorumu esastır.",
|
|
422
|
+
)
|
|
423
|
+
p.add_argument("--version", "--surum", action="version", version=f"tr-imar {__version__}")
|
|
424
|
+
sub = p.add_subparsers(dest="komut", metavar="KOMUT")
|
|
425
|
+
|
|
426
|
+
s = sub.add_parser("yogunluk", parents=[common], help="Taban alanı, emsal alanı, %%30 tavanı")
|
|
427
|
+
s.add_argument("--parsel", type=float, required=True, help="Net imar parseli alanı (m²)")
|
|
428
|
+
s.add_argument("--taks", type=float, help="Plandaki TAKS")
|
|
429
|
+
s.add_argument("--kaks", "--emsal", dest="kaks", type=float, help="Plandaki KAKS (emsal)")
|
|
430
|
+
s.add_argument("--kat", type=int, help="Kat adedi (bodrum hariç)")
|
|
431
|
+
s.add_argument("--nizam", choices=NIZAM_TYPES, default="ayrik", help="Yapı nizamı")
|
|
432
|
+
s.add_argument("--zarf-alani", type=float, help="Çekmeler sonrası zarf alanı (m²)")
|
|
433
|
+
s.set_defaults(func=_cmd_yogunluk)
|
|
434
|
+
|
|
435
|
+
s = sub.add_parser("cekme", parents=[common], help="Asgari ön/yan/arka bahçe mesafeleri")
|
|
436
|
+
s.add_argument("--kat", type=int, help="Zemin üstü bodrumlar dahil kat adedi")
|
|
437
|
+
s.add_argument("--yukseklik", type=float, help="Görünen bina yüksekliği (m)")
|
|
438
|
+
s.add_argument("--nizam", choices=NIZAM_TYPES, default="ayrik")
|
|
439
|
+
s.add_argument("--plan-on", type=float, help="Plan notundaki ön bahçe (m)")
|
|
440
|
+
s.add_argument("--plan-yan", type=float, help="Plan notundaki yan bahçe (m)")
|
|
441
|
+
s.add_argument("--plan-arka", type=float, help="Plan notundaki arka bahçe (m)")
|
|
442
|
+
s.add_argument("--kat-yuksekligi", type=float, help="60.50 m üstü için tipik kat yüksekliği")
|
|
443
|
+
s.set_defaults(func=_cmd_cekme)
|
|
444
|
+
|
|
445
|
+
s = sub.add_parser("yukseklik", parents=[common], help="Hmax, yol genişliğine göre kat, sınıf")
|
|
446
|
+
s.add_argument("--kat", type=int, help="Kat adedi (bodrum hariç)")
|
|
447
|
+
s.add_argument("--bolge", choices=ZONE_TYPES, default="konut", help="Kat yüksekliği bölgesi")
|
|
448
|
+
s.add_argument("--asma-kat", action="store_true", help="Asma katlı zemin kat")
|
|
449
|
+
s.add_argument("--subasman", type=float, help="Subasman yüksekliği (m, en çok 1.20)")
|
|
450
|
+
s.add_argument("--yol", type=float, help="Ön cephedeki yolun plan genişliği (m)")
|
|
451
|
+
s.add_argument(
|
|
452
|
+
"--siniflandir",
|
|
453
|
+
type=float,
|
|
454
|
+
metavar="BINA_H",
|
|
455
|
+
help="Bina yüksekliğine göre yüksek yapı sınıfı",
|
|
456
|
+
)
|
|
457
|
+
s.add_argument("--yapi-yuksekligi", type=float, help="Yapı yüksekliği (m)")
|
|
458
|
+
s.set_defaults(func=_cmd_yukseklik)
|
|
459
|
+
|
|
460
|
+
s = sub.add_parser("zarf", parents=[common], help="Parselde yapılaşma zarfı")
|
|
461
|
+
g = s.add_mutually_exclusive_group()
|
|
462
|
+
g.add_argument("--dikdortgen", metavar="CEPHExDERINLIK", help="Dikdörtgen parsel, ör. 20x30")
|
|
463
|
+
g.add_argument("--poligon", metavar="'x,y;x,y;...'", help="Parsel köşeleri (metrik)")
|
|
464
|
+
g.add_argument("--geojson", metavar="DOSYA", help="Parsel poligonu (GeoJSON, metrik)")
|
|
465
|
+
s.add_argument(
|
|
466
|
+
"--on-kenar",
|
|
467
|
+
type=int,
|
|
468
|
+
action="append",
|
|
469
|
+
metavar="I",
|
|
470
|
+
help="Yola cepheli kenar indisi (tekrarlanabilir)",
|
|
471
|
+
)
|
|
472
|
+
s.add_argument("--roller", help="Kenar rolleri, ör. on,yan,arka,bitisik")
|
|
473
|
+
s.add_argument("--kat", type=int)
|
|
474
|
+
s.add_argument("--yukseklik", type=float)
|
|
475
|
+
s.add_argument("--nizam", choices=NIZAM_TYPES, default="ayrik")
|
|
476
|
+
s.add_argument("--plan-on", type=float)
|
|
477
|
+
s.add_argument("--plan-yan", type=float)
|
|
478
|
+
s.add_argument("--plan-arka", type=float)
|
|
479
|
+
s.add_argument(
|
|
480
|
+
"--yontem",
|
|
481
|
+
choices=("parallel", "exact"),
|
|
482
|
+
default="parallel",
|
|
483
|
+
help="parallel: paralel yaklaşma sınırları; exact: Öklid (shapely)",
|
|
484
|
+
)
|
|
485
|
+
s.add_argument("--cikti", metavar="DOSYA", help="Zarfı GeoJSON olarak yaz")
|
|
486
|
+
s.set_defaults(func=_cmd_zarf)
|
|
487
|
+
|
|
488
|
+
s = sub.add_parser("otopark", parents=[common], help="Otopark ihtiyacı (Ek-1)")
|
|
489
|
+
s.add_argument("--liste", action="store_true", help="Otopark tablosunu listele")
|
|
490
|
+
s.add_argument(
|
|
491
|
+
"--daire",
|
|
492
|
+
action="append",
|
|
493
|
+
metavar="M2[,M2...]",
|
|
494
|
+
help="Daire brüt alanları (tekrarlanabilir veya virgüllü)",
|
|
495
|
+
)
|
|
496
|
+
s.add_argument(
|
|
497
|
+
"--kullanim",
|
|
498
|
+
action="append",
|
|
499
|
+
metavar="ANAHTAR=MIKTAR",
|
|
500
|
+
help="ör. dukkan=240, otel_diger=60 (oda), spor_salonu=800 (oturma)",
|
|
501
|
+
)
|
|
502
|
+
s.add_argument("--girdi", metavar="JSON", help="Kullanım listesi JSON dosyası")
|
|
503
|
+
s.add_argument("--tablo", metavar="JSON", help="Tablo değişiklikleri JSON dosyası")
|
|
504
|
+
s.add_argument("--katsayi", type=float, default=1.0, help="Yerel artırma katsayısı")
|
|
505
|
+
s.add_argument(
|
|
506
|
+
"--azaltma", type=float, default=0.0, help="Küçük parsel konut azaltması (0-0.5)"
|
|
507
|
+
)
|
|
508
|
+
s.set_defaults(func=_cmd_otopark)
|
|
509
|
+
|
|
510
|
+
s = sub.add_parser("kontrol", parents=[common], help="Proje JSON dosyası için uyum raporu")
|
|
511
|
+
s.add_argument("proje", help="Proje JSON dosyası ({'plan': {...}, 'proje': {...}})")
|
|
512
|
+
s.add_argument("--tablo", metavar="JSON", help="Otopark tablosu değişiklikleri")
|
|
513
|
+
s.add_argument("--yontem", choices=("parallel", "exact"), default="parallel")
|
|
514
|
+
s.set_defaults(func=_cmd_kontrol)
|
|
515
|
+
|
|
516
|
+
s = sub.add_parser("yapilandirma", parents=[common], help="Eşik değerlerini JSON yaz")
|
|
517
|
+
s.add_argument("--kaynaklar", action="store_true", help="Dayanak maddeleriyle birlikte")
|
|
518
|
+
s.set_defaults(func=_cmd_yapilandirma)
|
|
519
|
+
return p
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def main(argv: Optional[Sequence[str]] = None, out: Optional[TextIO] = None) -> int:
|
|
523
|
+
"""CLI giriş noktası. Dönüş: 0 başarılı/uygun, 1 aykırılık var, 2 hata."""
|
|
524
|
+
out = out if out is not None else sys.stdout
|
|
525
|
+
parser = build_parser()
|
|
526
|
+
args = parser.parse_args(argv)
|
|
527
|
+
if not getattr(args, "func", None):
|
|
528
|
+
parser.print_help(out)
|
|
529
|
+
return 2
|
|
530
|
+
try:
|
|
531
|
+
return int(args.func(args, out))
|
|
532
|
+
except ImarError as exc:
|
|
533
|
+
sys.stderr.write(f"tr-imar: hata: {exc}\n")
|
|
534
|
+
return 2
|
|
535
|
+
except (OSError, ValueError) as exc:
|
|
536
|
+
sys.stderr.write(f"tr-imar: hata: {exc}\n")
|
|
537
|
+
return 2
|