├── src ├── gui │ ├── .gitignore │ ├── tildy_gui.ico │ ├── tildy_gui.res │ ├── dialogs │ │ ├── tildy.gui.dialogs.addlayers.lrj │ │ ├── tildy.gui.dialogs.editareaname.lrj │ │ ├── tildy.gui.dialogs.editareaname.pas │ │ ├── tildy.gui.dialogs.addlayers.lfm │ │ ├── tildy.gui.dialogs.editareaname.lfm │ │ └── tildy.gui.dialogs.addlayers.pas │ ├── i18n │ │ ├── tildy.gui.i18n.strconsts.pas │ │ └── tildy.gui.i18n.runtime.pas │ ├── brand │ │ └── tildy.gui.brand.colors.pas │ ├── tildy_gui.lpr │ ├── forms │ │ ├── tildy.gui.forms.main.lrj │ │ └── tildy.gui.forms.main.pas │ └── tildy_gui.lpi ├── cli │ ├── tildy_cli.res │ ├── tildy.cli.options.pas │ ├── tildy_cli.lpi │ └── tildy_cli.lpr ├── resources │ ├── icons │ │ ├── dark_add.png │ │ ├── dark_up.png │ │ ├── light_up.png │ │ ├── dark_apply.png │ │ ├── dark_close.png │ │ ├── dark_down.png │ │ ├── dark_edit.png │ │ ├── dark_remove.png │ │ ├── light_add.png │ │ ├── light_apply.png │ │ ├── light_close.png │ │ ├── light_down.png │ │ ├── light_edit.png │ │ ├── tildy_logo.png │ │ ├── dark_edit_off.png │ │ ├── dark_language.png │ │ ├── dark_zoom_in.png │ │ ├── dark_zoom_out.png │ │ ├── light_remove.png │ │ ├── light_zoom_in.png │ │ ├── active_tile_info.png │ │ ├── dark_file_open.png │ │ ├── dark_file_save.png │ │ ├── dark_folder_open.png │ │ ├── dark_tile_info.png │ │ ├── light_edit_off.png │ │ ├── light_file_open.png │ │ ├── light_file_save.png │ │ ├── light_language.png │ │ ├── light_tile_info.png │ │ ├── light_zoom_out.png │ │ └── light_folder_open.png │ └── language │ │ ├── tildy_gui.en.mo │ │ ├── tildy_gui.pot │ │ ├── tildy_gui.en.po │ │ └── tildy_gui.ru.po ├── tildy.lpg ├── core │ ├── tildy.core.filters.pas │ ├── tildy.core.projections.pas │ └── tildy.core.engine.pas └── utilities │ └── tildy.utilities.time.pas ├── examples ├── monochromes │ ├── monochromes.ini │ ├── areas.ini │ ├── local_osm_and_railway.ini │ └── monochromes.sh ├── layers_osm_and_railway │ ├── layers.ini │ └── layers_osm_and_railway.sh ├── areas │ ├── areas.ini │ └── areas.sh └── custom_providers │ ├── custom_providers.sh │ └── config.ini ├── .gitignore ├── .github └── workflows │ └── snapshot.yml ├── README.md ├── README_RU.md ├── docs ├── RESTRICTIONS_RU.md ├── RESTRICTIONS.md ├── USAGE.md ├── USAGE_RU.md └── media │ └── logo.svg └── LICENSE /src/gui/.gitignore: -------------------------------------------------------------------------------- 1 | tildy_assistant 2 | -------------------------------------------------------------------------------- /src/cli/tildy_cli.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/cli/tildy_cli.res -------------------------------------------------------------------------------- /src/gui/tildy_gui.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/gui/tildy_gui.ico -------------------------------------------------------------------------------- /src/gui/tildy_gui.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/gui/tildy_gui.res -------------------------------------------------------------------------------- /examples/monochromes/monochromes.ini: -------------------------------------------------------------------------------- 1 | [Monochrome] 2 | color=#EFEFEF 3 | 4 | [Monochrome] 5 | color=#C9C9C9 -------------------------------------------------------------------------------- /src/resources/icons/dark_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_add.png -------------------------------------------------------------------------------- /src/resources/icons/dark_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_up.png -------------------------------------------------------------------------------- /src/resources/icons/light_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_up.png -------------------------------------------------------------------------------- /src/resources/icons/dark_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_apply.png -------------------------------------------------------------------------------- /src/resources/icons/dark_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_close.png -------------------------------------------------------------------------------- /src/resources/icons/dark_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_down.png -------------------------------------------------------------------------------- /src/resources/icons/dark_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_edit.png -------------------------------------------------------------------------------- /src/resources/icons/dark_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_remove.png -------------------------------------------------------------------------------- /src/resources/icons/light_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_add.png -------------------------------------------------------------------------------- /src/resources/icons/light_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_apply.png -------------------------------------------------------------------------------- /src/resources/icons/light_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_close.png -------------------------------------------------------------------------------- /src/resources/icons/light_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_down.png -------------------------------------------------------------------------------- /src/resources/icons/light_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_edit.png -------------------------------------------------------------------------------- /src/resources/icons/tildy_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/tildy_logo.png -------------------------------------------------------------------------------- /src/resources/icons/dark_edit_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_edit_off.png -------------------------------------------------------------------------------- /src/resources/icons/dark_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_language.png -------------------------------------------------------------------------------- /src/resources/icons/dark_zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_zoom_in.png -------------------------------------------------------------------------------- /src/resources/icons/dark_zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_zoom_out.png -------------------------------------------------------------------------------- /src/resources/icons/light_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_remove.png -------------------------------------------------------------------------------- /src/resources/icons/light_zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_zoom_in.png -------------------------------------------------------------------------------- /src/resources/icons/active_tile_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/active_tile_info.png -------------------------------------------------------------------------------- /src/resources/icons/dark_file_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_file_open.png -------------------------------------------------------------------------------- /src/resources/icons/dark_file_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_file_save.png -------------------------------------------------------------------------------- /src/resources/icons/dark_folder_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_folder_open.png -------------------------------------------------------------------------------- /src/resources/icons/dark_tile_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/dark_tile_info.png -------------------------------------------------------------------------------- /src/resources/icons/light_edit_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_edit_off.png -------------------------------------------------------------------------------- /src/resources/icons/light_file_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_file_open.png -------------------------------------------------------------------------------- /src/resources/icons/light_file_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_file_save.png -------------------------------------------------------------------------------- /src/resources/icons/light_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_language.png -------------------------------------------------------------------------------- /src/resources/icons/light_tile_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_tile_info.png -------------------------------------------------------------------------------- /src/resources/icons/light_zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_zoom_out.png -------------------------------------------------------------------------------- /src/resources/language/tildy_gui.en.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/language/tildy_gui.en.mo -------------------------------------------------------------------------------- /src/resources/icons/light_folder_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfilippenok/tildy/HEAD/src/resources/icons/light_folder_open.png -------------------------------------------------------------------------------- /examples/layers_osm_and_railway/layers.ini: -------------------------------------------------------------------------------- 1 | [Layer] 2 | provider=osm-standard 3 | filter=grayscale 4 | 5 | [Layer] 6 | provider=railway-standard -------------------------------------------------------------------------------- /examples/monochromes/areas.ini: -------------------------------------------------------------------------------- 1 | [Area] 2 | left=86.484375 3 | top=64.5861848033998 4 | right=104.23828125 5 | bottom=64.5861848033998 6 | 7 | -------------------------------------------------------------------------------- /examples/areas/areas.ini: -------------------------------------------------------------------------------- 1 | # Moscow 2 | [Area] 3 | left=37.1 4 | top=56 5 | right=38 6 | bottom=55.49 7 | 8 | # Yekaterinburg 9 | [Area] 10 | left=60 11 | top=57 12 | right=61 13 | bottom=56.6 -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.addlayers.lrj: -------------------------------------------------------------------------------- 1 | {"version":1,"strings":[ 2 | {"hash":108185698,"name":"tfaddlayers.caption","sourcebytes":[65,100,100,32,108,97,121,101,114],"value":"Add layer"} 3 | ]} 4 | -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.editareaname.lrj: -------------------------------------------------------------------------------- 1 | {"version":1,"strings":[ 2 | {"hash":3222533,"name":"tfeditareaname.caption","sourcebytes":[69,100,105,116,32,97,114,101,97,32,110,97,109,101],"value":"Edit area name"} 3 | ]} 4 | -------------------------------------------------------------------------------- /examples/areas/areas.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=$(dirname "$(realpath "$0")") 4 | 5 | ../../tildy -p osm-standard \ 6 | -z 0 \ 7 | -Z 3 \ 8 | -as areas.ini \ 9 | -o $script_dir/{p}/{z}/{x}/{y} \ 10 | -skip-missing -------------------------------------------------------------------------------- /examples/layers_osm_and_railway/layers_osm_and_railway.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=$(dirname "$(realpath "$0")") 4 | 5 | ../../tildy -layers layers.ini \ 6 | -out $script_dir/tiles/{p}/{z}/{x}/{y} \ 7 | -min-zoom 0 \ 8 | -max-zoom 2 \ 9 | -skip-missing -------------------------------------------------------------------------------- /examples/custom_providers/custom_providers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=$(dirname "$(realpath "$0")") 4 | 5 | ../../tildy -providers $script_dir/cconfig.ini \ 6 | -layers config.ini \ 7 | -out $script_dir/tiles/{p}/{z}/{x}/{y} \ 8 | -min-zoom 0 \ 9 | -max-zoom 2 \ 10 | -skip-missing -------------------------------------------------------------------------------- /examples/custom_providers/config.ini: -------------------------------------------------------------------------------- 1 | [Provider] 2 | ident=osm-standard-local 3 | name=OpenStreetMap-Mapnik 4 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 5 | 6 | [Provider] 7 | ident=railway-standard-local 8 | name=OpenRailwayMap-Standard 9 | url=http://localhost:7080/tile/{z}/{x}/{y}.png 10 | 11 | [Layer] 12 | provider=osm-standard-local 13 | filter=grayscale 14 | 15 | [Layer] 16 | provider=railway-standard-local -------------------------------------------------------------------------------- /examples/monochromes/local_osm_and_railway.ini: -------------------------------------------------------------------------------- 1 | [Provider] 2 | ident=osm-standard-local 3 | name=OpenStreetMap-Mapnik 4 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 5 | 6 | [Provider] 7 | ident=railway-standard-local 8 | name=OpenRailwayMap-Standard 9 | url=http://localhost:7080/tile/{z}/{x}/{y}.png 10 | 11 | [Layer] 12 | provider=osm-standard-local 13 | filter=grayscale 14 | 15 | [Layer] 16 | provider=railway-standard-local 17 | -------------------------------------------------------------------------------- /src/gui/i18n/tildy.gui.i18n.strconsts.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.i18n.StrConsts; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils; 9 | 10 | resourcestring 11 | SCancel = 'Cancel'; 12 | SApply = 'Apply'; 13 | SAdd = 'Add'; 14 | SZoom = 'Zoom'; 15 | SLanguage = 'Language'; 16 | SLat = 'Latitude'; 17 | SLon = 'Longitude'; 18 | 19 | 20 | implementation 21 | 22 | end. 23 | 24 | -------------------------------------------------------------------------------- /examples/monochromes/monochromes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_dir=$(dirname "$(realpath "$0")") 4 | 5 | ../../tildy -ls local_osm_and_railway.ini \ 6 | -ps local_osm_and_railway.ini \ 7 | -z 0 \ 8 | -Z 10 \ 9 | -skmono \ 10 | -ms monochromes.ini \ 11 | -as areas.ini \ 12 | -o $script_dir/tiles/{p}/{z}_{x}_{y} \ 13 | -skip-missing \ 14 | -skip-existing -------------------------------------------------------------------------------- /src/tildy.lpg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/gui/brand/tildy.gui.brand.colors.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.Brand.Colors; 2 | 3 | {$mode ObjFPC}{$H+} 4 | {$modeswitch advancedrecords} 5 | 6 | interface 7 | 8 | uses 9 | Classes, SysUtils, Graphics, System.UITypes; 10 | 11 | type 12 | 13 | BrandColors = record 14 | const 15 | Accent : TColor = TColor($00E48C3A); 16 | Secondary: TColor = TColor($00B47A37); 17 | end; 18 | 19 | function IsDarkTheme: Boolean; 20 | 21 | implementation 22 | 23 | function IsDarkTheme: Boolean; 24 | 25 | function _Level(AColor: TColor): double; 26 | begin 27 | Result:= Red(AColor)*0.3 + Green(AColor)*0.59 + Blue(AColor)*0.11; 28 | end; 29 | 30 | begin 31 | Result:= _Level(ColorToRGB(clWindow)) < _Level(ColorToRGB(clWindowText)); 32 | end; 33 | 34 | end. 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Lazarus gitignore 2 | # https://github.com/github/gitignore/blob/main/Global/Lazarus.gitignore 3 | 4 | # Lazarus compiler-generated binaries (safe to delete) 5 | *.exe 6 | *.dll 7 | *.so 8 | *.dylib 9 | *.lrs 10 | *.res 11 | *.compiled 12 | *.dbg 13 | *.ppu 14 | *.o 15 | *.or 16 | *.a 17 | 18 | # Lazarus autogenerated files (duplicated info) 19 | *.rst 20 | *.rsj 21 | *.lrt 22 | 23 | # Lazarus local files (user-specific info) 24 | *.lps 25 | 26 | # Lazarus backups and unit output folders. 27 | # These can be changed by user in Lazarus/project options. 28 | backup/ 29 | *.bak 30 | lib/ 31 | 32 | # Application bundle for Mac OS 33 | *.app/ 34 | 35 | # Project 36 | tildy 37 | tildy_gui 38 | tiles/ 39 | cache/ 40 | build/ 41 | bin/ 42 | 43 | # lazbuild 44 | *.swp 45 | -------------------------------------------------------------------------------- /src/gui/tildy_gui.lpr: -------------------------------------------------------------------------------- 1 | program tildy_gui; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | uses 6 | {$IFDEF UNIX} 7 | cthreads, 8 | {$ENDIF} 9 | {$IFDEF HASAMIGA} 10 | athreads, 11 | {$ENDIF} 12 | Interfaces, // this includes the LCL widgetset 13 | Forms, 14 | // Forms 15 | Tildy.GUI.Forms.Main, 16 | // Dialogs 17 | Tildy.GUI.Dialogs.AddLayers, 18 | Tildy.GUI.Dialogs.EditAreaName, 19 | // Translate 20 | Tildy.GUI.i18n.Runtime; 21 | 22 | {$R *.res} 23 | 24 | begin 25 | RequireDerivedFormResource :=True; 26 | Application.Title:='Tildy'; 27 | Application.Scaled:=True; 28 | Application.{%H-}MainFormOnTaskbar := True; 29 | Application.Initialize; 30 | SetGUILanguageFromSystem; 31 | Application.CreateForm(TfMain, fMain); 32 | Application.Run; 33 | end. 34 | 35 | -------------------------------------------------------------------------------- /src/core/tildy.core.filters.pas: -------------------------------------------------------------------------------- 1 | { 2 | Copyright (c) 2024-2025 Kirill Filippenok 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. } 15 | 16 | unit Tildy.Core.Filters; 17 | 18 | {$mode ObjFPC}{$H+} 19 | 20 | interface 21 | 22 | uses 23 | Classes, SysUtils, Tildy.Core.Engine, BGRABitmap; 24 | 25 | type 26 | 27 | { TFilterGrayscale } 28 | 29 | TFilterGrayscale = class(TInterfacedObject, IFilter) 30 | strict private 31 | procedure Grayscale(var ABGRABitmap: TBGRABitmap); 32 | public 33 | procedure Transform(var ABGRABitmap: TBGRABitmap); 34 | end; 35 | 36 | implementation 37 | 38 | { TFilterGrayscale } 39 | 40 | procedure TFilterGrayscale.Grayscale(var ABGRABitmap: TBGRABitmap); 41 | var 42 | LOld: TBGRABitmap; 43 | begin 44 | LOld := ABGRABitmap; 45 | ABGRABitmap := ABGRABitmap.FilterGrayscale(true); 46 | LOld.Free; 47 | end; 48 | 49 | procedure TFilterGrayscale.Transform(var ABGRABitmap: TBGRABitmap); 50 | begin 51 | if Assigned(ABGRABitmap) then 52 | Grayscale(ABGRABitmap); 53 | end; 54 | 55 | end. 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Build Snapshot 2 | on: 3 | push: 4 | paths: 5 | - 'tests/**' 6 | - 'src/**' 7 | - '.github/**' 8 | 9 | env: 10 | release_tag: snapshot 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | operating-system: [windows-latest, ubuntu-latest] 17 | runs-on: ${{ matrix.operating-system }} 18 | steps: 19 | - name: Checkout tildy 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Lazarus 23 | uses: gcarreno/setup-lazarus@v3 24 | with: 25 | lazarus-version: "dist" 26 | with-cache: false 27 | 28 | - name: Download BGRABitmapPack Windows 29 | if: ${{ matrix.operating-system == 'windows-latest' }} 30 | run: Invoke-WebRequest -Uri https://packages.lazarus-ide.org/BGRABitmap.zip -OutFile BGRABitmap.zip 31 | - name: Download BGRABitmapPack Linux 32 | if: ${{ matrix.operating-system == 'ubuntu-latest' }} 33 | run: wget https://packages.lazarus-ide.org/BGRABitmap.zip 34 | 35 | - name: Unzip BGRABitmapPack Windows 36 | if: ${{ matrix.operating-system == 'windows-latest' }} 37 | run: Expand-Archive -Path BGRABitmap.zip -DestinationPath . -Force 38 | - name: Unzip BGRABitmapPack Linux 39 | if: ${{ matrix.operating-system == 'ubuntu-latest' }} 40 | run: unzip BGRABitmap.zip 41 | 42 | - name: Build BGRABitmapPack4NoGUI 43 | run: lazbuild BGRABitmap/bgrabitmap/bgrabitmappack4nogui.lpk 44 | 45 | - name: Build tildy 46 | run: lazbuild -B --bm="Release" src/tildy.lpi 47 | -------------------------------------------------------------------------------- /src/gui/forms/tildy.gui.forms.main.lrj: -------------------------------------------------------------------------------- 1 | {"version":1,"strings":[ 2 | {"hash":5964473,"name":"tfmain.caption","sourcebytes":[84,105,108,100,121],"value":"Tildy"}, 3 | {"hash":7766822,"name":"tfmain.panmv.caption","sourcebytes":[112,97,110,77,86],"value":"panMV"}, 4 | {"hash":86572179,"name":"tfmain.lbllayers.caption","sourcebytes":[76,97,121,101,114,115],"value":"Layers"}, 5 | {"hash":4815333,"name":"tfmain.lblcache.caption","sourcebytes":[67,97,99,104,101],"value":"Cache"}, 6 | {"hash":171246139,"name":"tfmain.providervariationsmap.text","sourcebytes":[79,112,101,110,83,116,114,101,101,116,77,97,112,32,77,97,112,110,105,107],"value":"OpenStreetMap Mapnik"}, 7 | {"hash":158138290,"name":"tfmain.lblprovidermap.caption","sourcebytes":[80,114,111,118,105,100,101,114],"value":"Provider"}, 8 | {"hash":4754307,"name":"tfmain.lblareas.caption","sourcebytes":[65,114,101,97,115],"value":"Areas"}, 9 | {"hash":8170820,"name":"tfmain.tileinfoplugin.infomask","sourcebytes":[37,48,58,100,58,37,49,58,100],"value":"%0:d:%1:d"}, 10 | {"hash":84178580,"name":"tfmain.actareasimport.caption","sourcebytes":[73,109,112,111,114,116],"value":"Import"}, 11 | {"hash":80705172,"name":"tfmain.actareasexport.caption","sourcebytes":[69,120,112,111,114,116],"value":"Export"}, 12 | {"hash":195296268,"name":"tfmain.actselectall.caption","sourcebytes":[83,101,108,101,99,116,32,97,108,108],"value":"Select all"}, 13 | {"hash":206450904,"name":"tfmain.milangaugeen.caption","sourcebytes":[69,110,103,108,105,115,104],"value":"English"}, 14 | {"hash":52901513,"name":"tfmain.milangaugeru.caption","sourcebytes":[208,160,209,131,209,129,209,129,208,186,208,184,208,185],"value":"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"} 15 | ]} 16 | -------------------------------------------------------------------------------- /src/utilities/tildy.utilities.time.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.Utilities.Time; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils; 9 | 10 | function GetTickCountMS: Int64; 11 | function GetTickCountMCS: Int64; 12 | 13 | implementation 14 | 15 | {$IFDEF UNIX} 16 | uses 17 | Unix; 18 | 19 | function GetTickCountMS: Int64; 20 | var 21 | lTimeVal: TTimeVal; 22 | begin 23 | fpgettimeofday(@lTimeVal, nil); 24 | Result := lTimeVal.tv_sec * 1000 + Round(lTimeVal.tv_usec / 1000); 25 | end; 26 | 27 | function GetTickCountMCS: Int64; 28 | var 29 | lTimeVal: TTimeVal; 30 | begin 31 | fpgettimeofday(@lTimeVal, nil); 32 | Result := lTimeVal.tv_sec * 1000000 + lTimeVal.tv_usec; 33 | end; 34 | {$ENDIF} 35 | 36 | {$IFDEF WINDOWS} 37 | uses 38 | Windows, DateUtils; 39 | 40 | var 41 | QueryPerformanceSupported: Boolean; 42 | PerformanceCounter: int64; 43 | PerformanceFrequency: int64; // ticks in second 44 | 45 | function GetTickCountMS: Int64; 46 | var 47 | LTime: TTime; 48 | begin 49 | if QueryPerformanceSupported then 50 | begin 51 | QueryPerformanceCounter(Result); 52 | Result := Round(Result * (1000 / PerformanceFrequency)); 53 | end 54 | else 55 | Result := DateTimeToDosDateTime(Now()); 56 | end; 57 | 58 | function GetTickCountMCS: Int64; 59 | var 60 | LTime: TTime; 61 | begin 62 | if QueryPerformanceSupported then 63 | begin 64 | QueryPerformanceCounter(Result); 65 | Result := Round(Result * (1000000 / PerformanceFrequency)); 66 | end 67 | else 68 | Result := DateTimeToDosDateTime(Now()); 69 | end; 70 | {$ENDIF} 71 | 72 | initialization 73 | 74 | {$IFDEF WINDOWS} 75 | QueryPerformanceSupported := QueryPerformanceFrequency(PerformanceFrequency) and QueryPerformanceCounter(PerformanceCounter); 76 | {$ENDIF} 77 | 78 | end. 79 | 80 | -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.editareaname.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.Dialogs.EditAreaName; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons, 9 | // Translate 10 | LCLTranslator, Tildy.GUI.i18n.Runtime, Tildy.GUI.i18n.StrConsts; 11 | 12 | type 13 | 14 | { TfEditAreaName } 15 | 16 | TfEditAreaName = class(TForm, ILocalizableForm) 17 | edAreaName: TEdit; 18 | btnApply: TSpeedButton; 19 | btnCancel: TSpeedButton; 20 | procedure btnApplyClick(Sender: TObject); 21 | procedure btnCancelClick(Sender: TObject); 22 | procedure edAreaNameChange(Sender: TObject); 23 | procedure FormCreate(Sender: TObject); 24 | strict private 25 | FAreaName: String; 26 | procedure SetAreaName(AAreaName: String); 27 | public 28 | procedure TranslationChanged; 29 | property AreaName: String read FAreaName write SetAreaName; 30 | end; 31 | 32 | var 33 | fEditAreaName: TfEditAreaName; 34 | 35 | implementation 36 | 37 | {$R *.lfm} 38 | 39 | { TfEditAreaName } 40 | 41 | procedure TfEditAreaName.edAreaNameChange(Sender: TObject); 42 | begin 43 | FAreaName := edAreaName.Text; 44 | end; 45 | 46 | procedure TfEditAreaName.FormCreate(Sender: TObject); 47 | begin 48 | TranslationChanged; 49 | end; 50 | 51 | procedure TfEditAreaName.btnCancelClick(Sender: TObject); 52 | begin 53 | Self.Close; 54 | end; 55 | 56 | procedure TfEditAreaName.btnApplyClick(Sender: TObject); 57 | begin 58 | Self.ModalResult := mrOK; 59 | end; 60 | 61 | procedure TfEditAreaName.SetAreaName(AAreaName: String); 62 | begin 63 | if FAreaName = AAreaName then Exit; 64 | 65 | FAreaName := AAreaName; 66 | edAreaName.Text := FAreaName; 67 | end; 68 | 69 | procedure TfEditAreaName.TranslationChanged; 70 | begin 71 | btnApply.Caption := SAdd; 72 | btnCancel.Caption := SCancel; 73 | end; 74 | 75 | end. 76 | 77 | -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.addlayers.lfm: -------------------------------------------------------------------------------- 1 | object fAddLayers: TfAddLayers 2 | Left = 2662 3 | Height = 810 4 | Top = 32 5 | Width = 1080 6 | BorderStyle = bsDialog 7 | Caption = 'Add layer' 8 | ClientHeight = 810 9 | ClientWidth = 1080 10 | DesignTimePPI = 144 11 | Position = poOwnerFormCenter 12 | LCLVersion = '4.5.0.0' 13 | OnCreate = FormCreate 14 | object ProvidersList: TListBox 15 | Left = 0 16 | Height = 728 17 | Top = 0 18 | Width = 1080 19 | Align = alClient 20 | ItemHeight = 0 21 | MultiSelect = True 22 | TabOrder = 0 23 | OnDblClick = ProvidersListDblClick 24 | OnSelectionChange = ProvidersListSelectionChange 25 | end 26 | object panControl: TPanel 27 | Left = 0 28 | Height = 82 29 | Top = 728 30 | Width = 1080 31 | Align = alBottom 32 | AutoSize = True 33 | ClientHeight = 82 34 | ClientWidth = 1080 35 | TabOrder = 1 36 | object btnAdd: TSpeedButton 37 | AnchorSideRight.Control = panControl 38 | AnchorSideRight.Side = asrBottom 39 | Left = 1014 40 | Height = 50 41 | Top = 16 42 | Width = 50 43 | Anchors = [akTop, akRight] 44 | AutoSize = True 45 | BorderSpacing.Around = 15 46 | Enabled = False 47 | Images = fMain.ImagesPrimary 48 | ImageIndex = 6 49 | Layout = blGlyphRight 50 | Margin = 10 51 | Spacing = 10 52 | OnClick = btnAddClick 53 | end 54 | object btnCancel: TSpeedButton 55 | AnchorSideTop.Control = btnAdd 56 | AnchorSideTop.Side = asrCenter 57 | AnchorSideRight.Control = btnAdd 58 | Left = 949 59 | Height = 50 60 | Top = 16 61 | Width = 50 62 | Anchors = [akTop, akRight] 63 | AutoSize = True 64 | Images = fMain.ImagesPrimary 65 | ImageIndex = 7 66 | Layout = blGlyphRight 67 | Margin = 10 68 | Spacing = 10 69 | OnClick = btnCancelClick 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.editareaname.lfm: -------------------------------------------------------------------------------- 1 | object fEditAreaName: TfEditAreaName 2 | Left = 540 3 | Height = 188 4 | Top = 37 5 | Width = 790 6 | AutoSize = True 7 | BorderStyle = bsDialog 8 | Caption = 'Edit area name' 9 | ClientHeight = 188 10 | ClientWidth = 790 11 | DesignTimePPI = 144 12 | ParentFont = True 13 | Position = poOwnerFormCenter 14 | LCLVersion = '4.5.0.0' 15 | OnCreate = FormCreate 16 | object edAreaName: TEdit 17 | AnchorSideLeft.Control = Owner 18 | AnchorSideTop.Control = Owner 19 | AnchorSideRight.Control = Owner 20 | AnchorSideRight.Side = asrBottom 21 | Left = 15 22 | Height = 33 23 | Top = 15 24 | Width = 760 25 | HelpType = htKeyword 26 | Anchors = [akTop, akLeft, akRight] 27 | BorderSpacing.Around = 15 28 | Constraints.MinWidth = 600 29 | TabOrder = 0 30 | OnChange = edAreaNameChange 31 | end 32 | object btnApply: TSpeedButton 33 | AnchorSideTop.Control = edAreaName 34 | AnchorSideTop.Side = asrBottom 35 | AnchorSideRight.Control = Owner 36 | AnchorSideRight.Side = asrBottom 37 | Left = 725 38 | Height = 50 39 | Top = 63 40 | Width = 50 41 | Anchors = [akTop, akRight] 42 | AutoSize = True 43 | BorderSpacing.Right = 15 44 | BorderSpacing.Bottom = 15 45 | Images = fMain.ImagesPrimary 46 | ImageIndex = 6 47 | Layout = blGlyphRight 48 | Margin = 10 49 | Spacing = 10 50 | OnClick = btnApplyClick 51 | end 52 | object btnCancel: TSpeedButton 53 | AnchorSideTop.Control = btnApply 54 | AnchorSideTop.Side = asrCenter 55 | AnchorSideRight.Control = btnApply 56 | Left = 660 57 | Height = 50 58 | Top = 63 59 | Width = 50 60 | Anchors = [akTop, akRight] 61 | AutoSize = True 62 | BorderSpacing.Left = 15 63 | BorderSpacing.Right = 15 64 | Images = fMain.ImagesPrimary 65 | ImageIndex = 7 66 | Layout = blGlyphRight 67 | Margin = 10 68 | Spacing = 10 69 | OnClick = btnCancelClick 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 🇬🇧 English 3 | | 4 | 🇷🇺 Русский 5 |
6 |

7 | 8 |

9 |

tildy

10 |

11 | A CLI utility for download tiles from various map providers. 12 |

13 |
14 | 15 | ## Features 16 | 17 | - Download tiles: 18 | - Completely the entire map 19 | - By the specified zoom levels 20 | - By the selected area(-s) 21 | - Add Custom Providers 22 | - Save tiles name and structuring according to the specified template 23 | - Combining tiles from multiple providers into one common one 24 | - Set the final resolution of tiles 25 | 26 | ## Usage 27 | 28 | A [detailed list](./docs/USAGE.md) of available options and their possible applications with examples of the use of both individual options and their combinations. 29 | 30 | ## Supported platforms 31 | 32 | | OS | Bitness | Aviability | 33 | | ------------ | ----------------------- | ------------------------------------------------------------------------------- | 34 | | Linux | `64` | ✅ | 35 | | Windows 10, 11 | `64` | ✅ | 36 | 37 | 38 | ## Releases 39 | 40 | You can download the utility on the [releases tab](https://github.com/kfilippenok/tildy/releases). 41 | 42 | ## Restrictions 43 | 44 | There are some [restrictions](./docs/RESTRICTIONS.md) when downloading tiles from OpenStreetMap and its like. 45 | 46 | ## Kanban 47 | 48 | To display the work on the project, a [Kanban board](https://github.com/users/tildy/projects/1) is used, implemented as a Github project. 49 | 50 | ## Dependencies 51 | 52 | ### Platform 53 | - [FreePascal Compiler](https://www.freepascal.org/) >= 3.2.2 54 | - [Lazarus IDE](https://www.lazarus-ide.org/) >= 3.6 55 | 56 | ### Packages 57 | 58 | - BGRABitmapPack4NoGUI (часть BGRABitmapPack) 59 | 60 | ### Libraries 61 | 62 | #### Windows 63 | 64 | - OpenSSL 65 | 66 | #### Linux: 67 | 68 | - linux-vdso.so.1 69 | - libc.so.6 70 | - ld-linux-x86-64.so.2 -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 |
2 | 🇬🇧 English 3 | | 4 | 🇷🇺 Русский 5 |
6 |

7 | 8 |

9 |

tildy

10 |

11 | Утилита с командным интерфейсом для скачивания плиток карт от различных провайдеров. 12 |

13 |
14 | 15 | ## Возможности 16 | 17 | - Скачивание плиток: 18 | - Полностью всей карты 19 | - По указанным уровням зума 20 | - По указанной(-ым) области(-ям) 21 | - Добавление пользовательских провайдеров 22 | - Сохранение тайлов и структруирование по указанному шаблону 23 | - Обединение плиток нескольких провайдеров в одну общую 24 | - Настройка итогового разрешения изображений 25 | 26 | ## Использование 27 | 28 | [Детальный список](./docs/USAGE_RU.md) доступных опций и их возможностей с примерами использования и комбинирования. 29 | 30 | ## Поддерживаемые платформы 31 | 32 | | ОС | Разрядность | Доступность | 33 | | ------------ | ----------------------- | ------------------------------------------------------------------------------- | 34 | | Linux | `64` | ✅ | 35 | | Windows 10, 11 | `64` | ✅ | 36 | 37 | 38 | ## Релизы 39 | 40 | Готовые сборки можно скачать на [вкладке релизов](https://github.com/kfilippenok/tildy/releases). 41 | 42 | ## Ограничения 43 | 44 | При скачивании плиток от OpenStreetMap есть некоторые ограничения на скачивание. Какие именно это ограничения и как их обойти, читайте в [подробной инструкции](./docs/RESTRICTIONS_RU.md). 45 | 46 | ## Прогресс работы 47 | 48 | Для отображения процесса работы используется [Канбан доска](https://github.com/users/kfilippenok/projects/1) в виде прикреплённого проекта к репозиторию. 49 | 50 | ## Зависимости 51 | 52 | ### Платформа 53 | - [FreePascal Compiler](https://www.freepascal.org/) >= 3.2.2 54 | - [Lazarus IDE](https://www.lazarus-ide.org/) >= 3.6 55 | 56 | ### Пакеты 57 | 58 | - BGRABitmapPack4NoGUI (часть BGRABitmapPack) 59 | 60 | ### Библиотеки 61 | 62 | #### Windows 63 | 64 | - OpenSSL 65 | 66 | #### Linux: 67 | 68 | - linux-vdso.so.1 69 | - libc.so.6 70 | - ld-linux-x86-64.so.2 -------------------------------------------------------------------------------- /src/resources/language/tildy_gui.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "X-Generator: Poedit 3.7\n" 12 | 13 | #: tfaddlayers.caption 14 | msgid "Add layer" 15 | msgstr "" 16 | 17 | #: tfeditareaname.caption 18 | msgid "Edit area name" 19 | msgstr "" 20 | 21 | #: tfmain.actareasexport.caption 22 | msgid "Export" 23 | msgstr "" 24 | 25 | #: tfmain.actareasimport.caption 26 | msgid "Import" 27 | msgstr "" 28 | 29 | #: tfmain.actselectall.caption 30 | msgid "Select all" 31 | msgstr "" 32 | 33 | #: tfmain.caption 34 | msgid "Tildy" 35 | msgstr "" 36 | 37 | #: tfmain.lblareas.caption 38 | msgid "Areas" 39 | msgstr "" 40 | 41 | #: tfmain.lblcache.caption 42 | msgid "Cache" 43 | msgstr "" 44 | 45 | #: tfmain.lbllayers.caption 46 | msgid "Layers" 47 | msgstr "" 48 | 49 | #: tfmain.lblprovidermap.caption 50 | msgid "Provider" 51 | msgstr "" 52 | 53 | #: tfmain.milangaugeen.caption 54 | msgid "English" 55 | msgstr "" 56 | 57 | #: tfmain.milangaugeru.caption 58 | msgid "Русский" 59 | msgstr "" 60 | 61 | #: tfmain.panmv.caption 62 | msgid "panMV" 63 | msgstr "" 64 | 65 | #: tfmain.providervariationsmap.text 66 | msgid "OpenStreetMap Mapnik" 67 | msgstr "" 68 | 69 | #: tfmain.tileinfoplugin.infomask 70 | #, object-pascal-format 71 | msgid "%0:d:%1:d" 72 | msgstr "" 73 | 74 | #: tildy.gui.i18n.strconsts.sadd 75 | msgctxt "tildy.gui.i18n.strconsts.sadd" 76 | msgid "Add" 77 | msgstr "" 78 | 79 | #: tildy.gui.i18n.strconsts.sapply 80 | msgctxt "tildy.gui.i18n.strconsts.sapply" 81 | msgid "Apply" 82 | msgstr "" 83 | 84 | #: tildy.gui.i18n.strconsts.scancel 85 | msgctxt "tildy.gui.i18n.strconsts.scancel" 86 | msgid "Cancel" 87 | msgstr "" 88 | 89 | #: tildy.gui.i18n.strconsts.slanguage 90 | msgctxt "tildy.gui.i18n.strconsts.slanguage" 91 | msgid "Language" 92 | msgstr "" 93 | 94 | #: tildy.gui.i18n.strconsts.slat 95 | msgid "Latitude" 96 | msgstr "" 97 | 98 | #: tildy.gui.i18n.strconsts.slon 99 | msgid "Longitude" 100 | msgstr "" 101 | 102 | #: tildy.gui.i18n.strconsts.szoom 103 | msgid "Zoom" 104 | msgstr "" 105 | 106 | -------------------------------------------------------------------------------- /src/resources/language/tildy_gui.en.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: en\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.7\n" 13 | 14 | #: tfaddlayers.caption 15 | msgid "Add layer" 16 | msgstr "Add layer" 17 | 18 | #: tfeditareaname.caption 19 | msgid "Edit area name" 20 | msgstr "Edit area name" 21 | 22 | #: tfmain.actareasexport.caption 23 | msgid "Export" 24 | msgstr "Export" 25 | 26 | #: tfmain.actareasimport.caption 27 | msgid "Import" 28 | msgstr "Import" 29 | 30 | #: tfmain.actselectall.caption 31 | msgid "Select all" 32 | msgstr "Select all" 33 | 34 | #: tfmain.caption 35 | msgid "Tildy" 36 | msgstr "Tildy" 37 | 38 | #: tfmain.lblareas.caption 39 | msgid "Areas" 40 | msgstr "Areas" 41 | 42 | #: tfmain.lblcache.caption 43 | msgid "Cache" 44 | msgstr "Cache" 45 | 46 | #: tfmain.lbllayers.caption 47 | msgid "Layers" 48 | msgstr "Layers" 49 | 50 | #: tfmain.lblprovidermap.caption 51 | msgid "Provider" 52 | msgstr "Provider" 53 | 54 | #: tfmain.milangaugeen.caption 55 | msgid "English" 56 | msgstr "English" 57 | 58 | #: tfmain.milangaugeru.caption 59 | msgid "Русский" 60 | msgstr "" 61 | 62 | #: tfmain.panmv.caption 63 | msgid "panMV" 64 | msgstr "" 65 | 66 | #: tfmain.providervariationsmap.text 67 | msgid "OpenStreetMap Mapnik" 68 | msgstr "" 69 | 70 | #: tfmain.tileinfoplugin.infomask 71 | #, object-pascal-format 72 | msgid "%0:d:%1:d" 73 | msgstr "" 74 | 75 | #: tildy.gui.i18n.strconsts.sadd 76 | msgctxt "tildy.gui.i18n.strconsts.sadd" 77 | msgid "Add" 78 | msgstr "Add" 79 | 80 | #: tildy.gui.i18n.strconsts.sapply 81 | msgctxt "tildy.gui.i18n.strconsts.sapply" 82 | msgid "Apply" 83 | msgstr "Apply" 84 | 85 | #: tildy.gui.i18n.strconsts.scancel 86 | msgctxt "tildy.gui.i18n.strconsts.scancel" 87 | msgid "Cancel" 88 | msgstr "Cancel" 89 | 90 | #: tildy.gui.i18n.strconsts.slanguage 91 | msgctxt "tildy.gui.i18n.strconsts.slanguage" 92 | msgid "Language" 93 | msgstr "Language" 94 | 95 | #: tildy.gui.i18n.strconsts.slat 96 | msgid "Latitude" 97 | msgstr "Latitude" 98 | 99 | #: tildy.gui.i18n.strconsts.slon 100 | msgid "Longitude" 101 | msgstr "Longitude" 102 | 103 | #: tildy.gui.i18n.strconsts.szoom 104 | msgid "Zoom" 105 | msgstr "Zoom" 106 | -------------------------------------------------------------------------------- /src/resources/language/tildy_gui.ru.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: ru\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.7\n" 13 | 14 | #: tfaddlayers.caption 15 | msgid "Add layer" 16 | msgstr "Добавить слой" 17 | 18 | #: tfeditareaname.caption 19 | msgid "Edit area name" 20 | msgstr "Изменить название области" 21 | 22 | #: tfmain.actareasexport.caption 23 | msgid "Export" 24 | msgstr "Экспорт" 25 | 26 | #: tfmain.actareasimport.caption 27 | msgid "Import" 28 | msgstr "Импорт" 29 | 30 | #: tfmain.actselectall.caption 31 | msgid "Select all" 32 | msgstr "Выбрать все" 33 | 34 | #: tfmain.caption 35 | msgid "Tildy" 36 | msgstr "" 37 | 38 | #: tfmain.lblareas.caption 39 | msgid "Areas" 40 | msgstr "Области" 41 | 42 | #: tfmain.lblcache.caption 43 | msgid "Cache" 44 | msgstr "Кэш" 45 | 46 | #: tfmain.lbllayers.caption 47 | msgid "Layers" 48 | msgstr "Слои" 49 | 50 | #: tfmain.lblprovidermap.caption 51 | msgid "Provider" 52 | msgstr "Провайдер" 53 | 54 | #: tfmain.milangaugeen.caption 55 | msgid "English" 56 | msgstr "" 57 | 58 | #: tfmain.milangaugeru.caption 59 | msgid "Русский" 60 | msgstr "" 61 | 62 | #: tfmain.panmv.caption 63 | msgid "panMV" 64 | msgstr "" 65 | 66 | #: tfmain.providervariationsmap.text 67 | msgid "OpenStreetMap Mapnik" 68 | msgstr "" 69 | 70 | #: tfmain.tileinfoplugin.infomask 71 | #, object-pascal-format 72 | msgid "%0:d:%1:d" 73 | msgstr "" 74 | 75 | #: tildy.gui.i18n.strconsts.sadd 76 | msgctxt "tildy.gui.i18n.strconsts.sadd" 77 | msgid "Add" 78 | msgstr "Добавить" 79 | 80 | #: tildy.gui.i18n.strconsts.sapply 81 | msgctxt "tildy.gui.i18n.strconsts.sapply" 82 | msgid "Apply" 83 | msgstr "Применить" 84 | 85 | #: tildy.gui.i18n.strconsts.scancel 86 | msgctxt "tildy.gui.i18n.strconsts.scancel" 87 | msgid "Cancel" 88 | msgstr "Отмена" 89 | 90 | #: tildy.gui.i18n.strconsts.slanguage 91 | msgctxt "tildy.gui.i18n.strconsts.slanguage" 92 | msgid "Language" 93 | msgstr "Язык" 94 | 95 | #: tildy.gui.i18n.strconsts.slat 96 | msgid "Latitude" 97 | msgstr "Широта" 98 | 99 | #: tildy.gui.i18n.strconsts.slon 100 | msgid "Longitude" 101 | msgstr "Долгота" 102 | 103 | #: tildy.gui.i18n.strconsts.szoom 104 | msgid "Zoom" 105 | msgstr "Зум" 106 | 107 | -------------------------------------------------------------------------------- /src/core/tildy.core.projections.pas: -------------------------------------------------------------------------------- 1 | { 2 | Copyright (c) 2024-2025 Kirill Filippenok 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. } 15 | 16 | unit Tildy.Core.Projections; 17 | 18 | {$mode ObjFPC}{$H+} 19 | 20 | interface 21 | 22 | uses 23 | Classes, SysUtils, Tildy.Core.Engine; 24 | 25 | type 26 | 27 | { TEPSG3857 } 28 | 29 | TEPSG3857 = class(TInterfacedObject, IProjection) 30 | const 31 | // https://epsg.io/3857 32 | LAT_MIN = -85.0511; 33 | LAT_MAX = 85.0511; 34 | LON_MIN = -179.99999; 35 | LON_MAX = 179.99999; 36 | public 37 | function MinLat: Extended; 38 | function MaxLat: Extended; 39 | function MinLon: Extended; 40 | function MaxLon: Extended; 41 | function CalcTileX(const AZoom: Byte; const ALongitude: Extended): QWord; 42 | function CalcTileY(const AZoom: Byte; const ALatitude: Extended): QWord; 43 | end; 44 | 45 | implementation 46 | 47 | uses 48 | Math; 49 | 50 | { TEPSG3857 } 51 | 52 | function TEPSG3857.MinLat: Extended; 53 | begin 54 | Result := LAT_MIN; 55 | end; 56 | 57 | function TEPSG3857.MaxLat: Extended; 58 | begin 59 | Result := LAT_MAX; 60 | end; 61 | 62 | function TEPSG3857.MinLon: Extended; 63 | begin 64 | Result := LON_MIN; 65 | end; 66 | 67 | function TEPSG3857.MaxLon: Extended; 68 | begin 69 | Result := LON_MAX; 70 | end; 71 | 72 | function TEPSG3857.CalcTileX(const AZoom: Byte; const ALongitude: Extended): QWord; 73 | var 74 | n: Extended; 75 | begin 76 | if AZoom = 0 then Exit(0); 77 | 78 | n := Power(2, AZoom); 79 | Result := Trunc(((ALongitude + 180) / 360) * n); 80 | end; 81 | 82 | function TEPSG3857.CalcTileY(const AZoom: Byte; const ALatitude: Extended): QWord; 83 | var 84 | lat_rad, n, x1, x2, x3, x4, x5: Extended; 85 | begin 86 | if AZoom = 0 then Exit(0); 87 | 88 | n := Power(2, AZoom); 89 | lat_rad := DegToRad(ALatitude); 90 | x1 := Tan(lat_rad); 91 | x2 := ArcSinH(x1); 92 | x3 := x2 / Pi; 93 | x4 := (1 - x3); 94 | x5 := x4 / 2.0; 95 | Result := Trunc(x5 * n); 96 | end; 97 | 98 | end. 99 | 100 | -------------------------------------------------------------------------------- /src/gui/dialogs/tildy.gui.dialogs.addlayers.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.Dialogs.AddLayers; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, 9 | Buttons, 10 | // Translate 11 | LCLTranslator, Tildy.GUI.i18n.Runtime, Tildy.GUI.i18n.StrConsts, 12 | // MapView 13 | mvMapViewer, mvDrawingEngine, mvMapProvider; 14 | 15 | type 16 | 17 | { TfAddLayers } 18 | 19 | TfAddLayers = class(TForm, ILocalizableForm) 20 | ProvidersList: TListBox; 21 | panControl: TPanel; 22 | btnAdd: TSpeedButton; 23 | btnCancel: TSpeedButton; 24 | procedure btnAddClick(Sender: TObject); 25 | procedure btnCancelClick(Sender: TObject); 26 | procedure FormCreate(Sender: TObject); 27 | procedure ProvidersListDblClick(Sender: TObject); 28 | procedure ProvidersListSelectionChange(Sender: TObject; User: boolean); 29 | private 30 | MapView: TMapView; 31 | public 32 | procedure TranslationChanged; 33 | constructor Create(AMapView: TMapView); reintroduce; 34 | end; 35 | 36 | var 37 | fAddLayers: TfAddLayers; 38 | 39 | implementation 40 | 41 | {$R *.lfm} 42 | 43 | { TfAddLayers } 44 | 45 | procedure TfAddLayers.btnCancelClick(Sender: TObject); 46 | begin 47 | Self.Close; 48 | end; 49 | 50 | procedure TfAddLayers.FormCreate(Sender: TObject); 51 | begin 52 | TranslationChanged; 53 | end; 54 | 55 | procedure TfAddLayers.ProvidersListDblClick(Sender: TObject); 56 | begin 57 | if (ProvidersList.ItemIndex = -1) then Exit; 58 | 59 | btnAdd.Click; 60 | end; 61 | 62 | procedure TfAddLayers.ProvidersListSelectionChange(Sender: TObject; 63 | User: boolean); 64 | begin 65 | btnAdd.Enabled := True; 66 | end; 67 | 68 | procedure TfAddLayers.TranslationChanged; 69 | begin 70 | btnAdd.Caption := SAdd; 71 | btnCancel.Caption := SCancel; 72 | end; 73 | 74 | procedure TfAddLayers.btnAddClick(Sender: TObject); 75 | var 76 | i: Integer; 77 | LMapLayer: TMapLayer; 78 | begin 79 | for i := 0 to ProvidersList.Count-1 do 80 | begin 81 | if ProvidersList.Selected[i] then 82 | begin 83 | LMapLayer := MapView.Layers.Add as TMapLayer; 84 | LMapLayer.MapProvider := ProvidersList.Items[ProvidersList.ItemIndex]; 85 | LMapLayer.DrawMode := idmUseSourceAlpha; 86 | LMapLayer.UseThreads := MapView.UseThreads; 87 | LMapLayer.Visible := True; 88 | end; 89 | end; 90 | Self.ModalResult := mrOK; 91 | end; 92 | 93 | constructor TfAddLayers.Create(AMapView: TMapView); 94 | begin 95 | inherited Create(Application); 96 | 97 | MapView := AMapView; 98 | MapProvidersToSortedStrings(ProvidersList.Items); 99 | end; 100 | 101 | end. 102 | 103 | -------------------------------------------------------------------------------- /src/gui/i18n/tildy.gui.i18n.runtime.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.i18n.Runtime; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Forms, Classes, SysUtils, Controls, LResources, 9 | {$IFDEF WINDOWS} 10 | Windows, 11 | {$ENDIF} 12 | // Translate 13 | LCLTranslator, Translations; 14 | 15 | type 16 | ILocalizableForm = interface 17 | ['{3C92427E-D847-4FDC-932F-E4733B1625E2}'] 18 | procedure TranslationChanged; 19 | end; 20 | 21 | var 22 | DefaultGUILanguageCode: String = 'en'; 23 | 24 | function SetGUILanguageFromResource(const ABaseName, ALanguageCode: string; 25 | AForm: TCustomForm = nil): Boolean; 26 | procedure SetGUILanguage(const ALanguageCode: string = ''); 27 | procedure SetGUILanguageFromSystem; 28 | 29 | implementation 30 | 31 | function SetGUILanguageFromResource(const ABaseName, ALanguageCode: string; 32 | AForm: TCustomForm = nil): Boolean; 33 | var 34 | ResStream : TResourceStream = nil; 35 | PoStream : TStringStream = nil; 36 | PoFile : TPOFile = nil; 37 | Translator : TPOTranslator = nil; 38 | LocForm : ILocalizableForm; 39 | i : Integer; 40 | LangToTry : String; 41 | IsLangFound : Boolean = False; 42 | IsResTranslated: Boolean = False; 43 | begin 44 | Result := False; 45 | 46 | LangToTry := Trim(ALanguageCode); 47 | if LangToTry.IsEmpty then 48 | LangToTry := DefaultGUILanguageCode; 49 | 50 | try 51 | try 52 | ResStream := TResourceStream.Create(HInstance, 53 | ABaseName + '.' + LangToTry, RT_RCDATA); 54 | IsLangFound := True; 55 | except 56 | FreeAndNil(ResStream); 57 | ResStream := TResourceStream.Create(HInstance, 58 | ABaseName + '.' + DefaultGUILanguageCode, RT_RCDATA); 59 | end; 60 | 61 | PoStream := TStringStream.Create(''); 62 | ResStream.SaveToStream(PoStream); 63 | 64 | PoFile := TPOFile.Create(False); 65 | PoFile.ReadPOText(PoStream.DataString); 66 | 67 | IsResTranslated := TranslateResourceStrings(PoFile); 68 | 69 | if (not IsResTranslated and not IsLangFound) then 70 | Exception.Create(String.Empty); 71 | 72 | Translator := TPOTranslator.Create(PoFile); 73 | if Assigned(LRSTranslator) then 74 | FreeAndNil(LRSTranslator); 75 | LRSTranslator := Translator; 76 | 77 | if Assigned(AForm) then 78 | Translator.UpdateTranslation(AForm) 79 | else 80 | begin 81 | for i := 0 to Screen.CustomFormCount - 1 do 82 | begin 83 | Translator.UpdateTranslation(Screen.CustomForms[i]); 84 | if Supports(Screen.CustomForms[I], ILocalizableForm, LocForm) then 85 | LocForm.TranslationChanged; 86 | end; 87 | for i := 0 to Screen.DataModuleCount - 1 do 88 | Translator.UpdateTranslation(Screen.DataModules[i]); 89 | end; 90 | 91 | Result := True; 92 | finally 93 | if Translator = nil then 94 | begin 95 | FreeAndNil(PoFile); 96 | if Assigned(LRSTranslator) then 97 | begin 98 | LRSTranslator.Free; 99 | LRSTranslator := nil; 100 | end; 101 | end; 102 | 103 | FreeAndNil(PoStream); 104 | FreeAndNil(ResStream); 105 | end; 106 | end; 107 | 108 | procedure SetGUILanguage(const ALanguageCode: string); 109 | begin 110 | if ALanguageCode.IsEmpty then 111 | SetGUILanguageFromResource('tildy_gui', DefaultGUILanguageCode) 112 | else 113 | SetGUILanguageFromResource('tildy_gui', ALanguageCode); 114 | end; 115 | 116 | procedure SetGUILanguageFromSystem; 117 | begin 118 | SetGUILanguage(GetLanguageID.LanguageCode); 119 | end; 120 | 121 | end. 122 | -------------------------------------------------------------------------------- /src/cli/tildy.cli.options.pas: -------------------------------------------------------------------------------- 1 | { 2 | Copyright (c) 2024-2025 Kirill Filippenok 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. } 15 | 16 | unit Tildy.CLI.Options; 17 | 18 | {$mode ObjFPC}{$H+} 19 | {$modeswitch typehelpers} 20 | 21 | interface 22 | 23 | uses 24 | Classes, SysUtils; 25 | 26 | type 27 | 28 | EOption = class(Exception); 29 | EOpProvider = class(EOption); 30 | EOpProviders = class(EOption); 31 | EOpLayers = class(EOption); 32 | EOpOutput = class(EOption); 33 | EOpMinZoom = class(EOption); 34 | EOpMaxZoom = class(EOption); 35 | EOpLeft = class(EOption); 36 | EOpTop = class(EOption); 37 | EOpRight = class(EOption); 38 | EOpBottom = class(EOption); 39 | EOpTileRes = class(EOption); 40 | EOpFilter = class(EOption); 41 | EOpBoundingBox = class(EOption); 42 | EOpCache = class(EOption); 43 | EOpAreas = class(EOption); 44 | EOpMonochrome = class(EOption); 45 | EOpMonochromes = class(EOption); 46 | 47 | TOptionKind = (okHelp, 48 | okProvider, 49 | okProviders, 50 | okLayers, 51 | okOut, 52 | okMinZoom, 53 | okMaxZoom, 54 | okLeft, 55 | okTop, 56 | okRight, 57 | okBottom, 58 | okShowFileType, 59 | okTileRes, 60 | okSkipMissing, 61 | okSkipExisting, 62 | okFilter, 63 | okVersion, 64 | okBoundingBox, 65 | okCache, 66 | okUseCacheOnly, 67 | okAreas, 68 | okMonochrome, 69 | okMonochromes); 70 | 71 | TOptions = Set of TOptionKind; 72 | 73 | { THelperOptionKind } 74 | 75 | THelperOptionKind = type Helper for TOptionKind 76 | function Name : String; 77 | function Ident: String; 78 | end; 79 | 80 | const 81 | OptionName: array [TOptionKind] of String = 82 | ( 83 | 'help', 84 | 'provider', 85 | 'providers', 86 | 'layers', 87 | 'out', 88 | 'min-zoom', 89 | 'max-zoom', 90 | 'left', 91 | 'top', 92 | 'right', 93 | 'bottom', 94 | 'show-file-type', 95 | 'tile-res', 96 | 'skip-missing', 97 | 'skip-existing', 98 | 'filter', 99 | 'version', 100 | 'bbox', 101 | 'cache', 102 | 'use,cache-only', 103 | 'areas', 104 | 'monochrome', 105 | 'monochromes' 106 | ); 107 | 108 | OptionIdent: array [TOptionKind] of String = 109 | ( 110 | 'h', 111 | 'p', 112 | 'ps', 113 | 'ls', 114 | 'o', 115 | 'z', 116 | 'Z', 117 | 'l', 118 | 't', 119 | 'r', 120 | 'b', 121 | 'sft', 122 | 'res', 123 | 'skm', 124 | 'ske', 125 | 'f', 126 | 'v', 127 | 'bb', 128 | 'c', 129 | 'uco', 130 | 'as', 131 | 'm', 132 | 'ms' 133 | ); 134 | 135 | implementation 136 | 137 | { THelperOptionKind } 138 | 139 | function THelperOptionKind.Name: String; 140 | begin 141 | Result := OptionName[Self]; 142 | end; 143 | 144 | function THelperOptionKind.Ident: String; 145 | begin 146 | Result := OptionIdent[Self]; 147 | end; 148 | 149 | end. 150 | 151 | -------------------------------------------------------------------------------- /src/cli/tildy_cli.lpi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <UseAppBundle Value="False"/> 14 | <ResourceType Value="res"/> 15 | </General> 16 | <VersionInfo> 17 | <UseVersionInfo Value="True"/> 18 | <MajorVersionNr Value="1"/> 19 | <MinorVersionNr Value="2"/> 20 | <StringTable LegalCopyright="Apache 2.0"/> 21 | </VersionInfo> 22 | <BuildModes> 23 | <Item Name="Debug" Default="True"/> 24 | <Item Name="Release"> 25 | <CompilerOptions> 26 | <Version Value="11"/> 27 | <Target> 28 | <Filename Value="../../bin/$(TargetOS)/debug/tildy"/> 29 | </Target> 30 | <SearchPaths> 31 | <IncludeFiles Value="$(ProjOutDir)"/> 32 | <OtherUnitFiles Value="../core;../utilities;../threading"/> 33 | <UnitOutputDirectory Value="../../build/$(TargetOS)"/> 34 | </SearchPaths> 35 | <CodeGeneration> 36 | <SmartLinkUnit Value="True"/> 37 | <Optimizations> 38 | <OptimizationLevel Value="3"/> 39 | </Optimizations> 40 | </CodeGeneration> 41 | <Linking> 42 | <Debugging> 43 | <GenerateDebugInfo Value="False"/> 44 | <RunWithoutDebug Value="True"/> 45 | <StripSymbols Value="True"/> 46 | </Debugging> 47 | <LinkSmart Value="True"/> 48 | </Linking> 49 | <Other> 50 | <CustomOptions Value="-B 51 | -dBGRABITMAP_DONT_USE_LCL"/> 52 | </Other> 53 | </CompilerOptions> 54 | </Item> 55 | </BuildModes> 56 | <PublishOptions> 57 | <Version Value="2"/> 58 | <UseFileFilters Value="True"/> 59 | </PublishOptions> 60 | <RunParams> 61 | <FormatVersion Value="2"/> 62 | </RunParams> 63 | <RequiredPackages> 64 | <Item> 65 | <PackageName Value="BGRABitmapPack4NoGUI"/> 66 | <MinVersion Major="11" Minor="6" Release="2" Valid="True"/> 67 | </Item> 68 | </RequiredPackages> 69 | <Units> 70 | <Unit> 71 | <Filename Value="tildy_cli.lpr"/> 72 | <IsPartOfProject Value="True"/> 73 | </Unit> 74 | <Unit> 75 | <Filename Value="tildy.cli.options.pas"/> 76 | <IsPartOfProject Value="True"/> 77 | <UnitName Value="Tildy.CLI.Options"/> 78 | </Unit> 79 | <Unit> 80 | <Filename Value="../core/tildy.core.engine.pas"/> 81 | <IsPartOfProject Value="True"/> 82 | <UnitName Value="Tildy.Core.Engine"/> 83 | </Unit> 84 | <Unit> 85 | <Filename Value="../core/tildy.core.filters.pas"/> 86 | <IsPartOfProject Value="True"/> 87 | </Unit> 88 | <Unit> 89 | <Filename Value="../core/tildy.core.projections.pas"/> 90 | <IsPartOfProject Value="True"/> 91 | <UnitName Value="Tildy.Core.Projections"/> 92 | </Unit> 93 | <Unit> 94 | <Filename Value="../utilities/tildy.utilities.time.pas"/> 95 | <IsPartOfProject Value="True"/> 96 | </Unit> 97 | <Unit> 98 | <Filename Value="../threading/tildy.threading.simpledownloader.pas"/> 99 | <IsPartOfProject Value="True"/> 100 | <UnitName Value="Tildy.Threading.SimpleDownloader"/> 101 | </Unit> 102 | </Units> 103 | </ProjectOptions> 104 | <CompilerOptions> 105 | <Version Value="11"/> 106 | <Target> 107 | <Filename Value="../../bin/$(TargetOS)/debug/tildy"/> 108 | </Target> 109 | <SearchPaths> 110 | <IncludeFiles Value="$(ProjOutDir)"/> 111 | <OtherUnitFiles Value="../core;../utilities;../threading"/> 112 | <UnitOutputDirectory Value="../../build/$(TargetOS)"/> 113 | </SearchPaths> 114 | <Parsing> 115 | <SyntaxOptions> 116 | <IncludeAssertionCode Value="True"/> 117 | </SyntaxOptions> 118 | </Parsing> 119 | <CodeGeneration> 120 | <Checks> 121 | <IOChecks Value="True"/> 122 | <RangeChecks Value="True"/> 123 | <OverflowChecks Value="True"/> 124 | <StackChecks Value="True"/> 125 | </Checks> 126 | <VerifyObjMethodCallValidity Value="True"/> 127 | </CodeGeneration> 128 | <Linking> 129 | <Debugging> 130 | <DebugInfoType Value="dsDwarf3"/> 131 | <UseHeaptrc Value="True"/> 132 | <TrashVariables Value="True"/> 133 | </Debugging> 134 | </Linking> 135 | <Other> 136 | <CustomOptions Value="-dDEBUG 137 | -B 138 | -dBGRABITMAP_DONT_USE_LCL"/> 139 | </Other> 140 | </CompilerOptions> 141 | <Debugging> 142 | <Exceptions> 143 | <Item> 144 | <Name Value="EAbort"/> 145 | </Item> 146 | <Item> 147 | <Name Value="ECodetoolError"/> 148 | </Item> 149 | <Item> 150 | <Name Value="EFOpenError"/> 151 | </Item> 152 | </Exceptions> 153 | </Debugging> 154 | </CONFIG> 155 | -------------------------------------------------------------------------------- /src/gui/tildy_gui.lpi: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <CONFIG> 3 | <ProjectOptions> 4 | <Version Value="12"/> 5 | <General> 6 | <SessionStorage Value="InProjectDir"/> 7 | <Title Value="Tildy"/> 8 | <Scaled Value="True"/> 9 | <ResourceType Value="res"/> 10 | <UseXPManifest Value="True"/> 11 | <XPManifest> 12 | <DpiAware Value="True"/> 13 | </XPManifest> 14 | <Icon Value="0"/> 15 | <Resources Count="2"> 16 | <Resource_0 FileName="..\resources\language\tildy_gui.ru.po" Type="RCDATA" ResourceName="TILDY_GUI.RU"/> 17 | <Resource_1 FileName="..\resources\language\tildy_gui.en.po" Type="RCDATA" ResourceName="TILDY_GUI.EN"/> 18 | </Resources> 19 | </General> 20 | <i18n> 21 | <EnableI18N Value="True"/> 22 | <OutDir Value="../resources/language"/> 23 | </i18n> 24 | <BuildModes> 25 | <Item Name="Debug" Default="True"/> 26 | <Item Name="Release"> 27 | <CompilerOptions> 28 | <Version Value="11"/> 29 | <Target> 30 | <Filename Value="../../bin/$(TargetOS)/release/tildy_gui"/> 31 | </Target> 32 | <SearchPaths> 33 | <IncludeFiles Value="$(ProjOutDir)"/> 34 | <OtherUnitFiles Value="dialogs;forms;brand;i18n"/> 35 | <UnitOutputDirectory Value="../../build/$(TargetOS)/release"/> 36 | </SearchPaths> 37 | <CodeGeneration> 38 | <SmartLinkUnit Value="True"/> 39 | <Optimizations> 40 | <OptimizationLevel Value="3"/> 41 | </Optimizations> 42 | </CodeGeneration> 43 | <Linking> 44 | <Debugging> 45 | <GenerateDebugInfo Value="False"/> 46 | <RunWithoutDebug Value="True"/> 47 | <StripSymbols Value="True"/> 48 | </Debugging> 49 | <LinkSmart Value="True"/> 50 | <Options> 51 | <Win32> 52 | <GraphicApplication Value="True"/> 53 | </Win32> 54 | </Options> 55 | </Linking> 56 | </CompilerOptions> 57 | </Item> 58 | </BuildModes> 59 | <PublishOptions> 60 | <Version Value="2"/> 61 | <UseFileFilters Value="True"/> 62 | </PublishOptions> 63 | <RunParams> 64 | <FormatVersion Value="2"/> 65 | </RunParams> 66 | <RequiredPackages> 67 | <Item> 68 | <PackageName Value="industrial"/> 69 | </Item> 70 | <Item> 71 | <PackageName Value="lazmapviewer_bgra"/> 72 | </Item> 73 | <Item> 74 | <PackageName Value="FCL"/> 75 | </Item> 76 | <Item> 77 | <PackageName Value="lazMapViewerPkg"/> 78 | </Item> 79 | <Item> 80 | <PackageName Value="LCL"/> 81 | </Item> 82 | </RequiredPackages> 83 | <Units> 84 | <Unit> 85 | <Filename Value="tildy_gui.lpr"/> 86 | <IsPartOfProject Value="True"/> 87 | </Unit> 88 | <Unit> 89 | <Filename Value="forms/tildy.gui.forms.main.pas"/> 90 | <IsPartOfProject Value="True"/> 91 | <ComponentName Value="fMain"/> 92 | <HasResources Value="True"/> 93 | <ResourceBaseClass Value="Form"/> 94 | <UnitName Value="Tildy.GUI.Forms.Main"/> 95 | </Unit> 96 | <Unit> 97 | <Filename Value="brand/tildy.gui.brand.colors.pas"/> 98 | <IsPartOfProject Value="True"/> 99 | <UnitName Value="Tildy.GUI.Brand.Colors"/> 100 | </Unit> 101 | <Unit> 102 | <Filename Value="dialogs/tildy.gui.dialogs.addlayers.pas"/> 103 | <IsPartOfProject Value="True"/> 104 | <ComponentName Value="fAddLayers"/> 105 | <HasResources Value="True"/> 106 | <ResourceBaseClass Value="Form"/> 107 | <UnitName Value="Tildy.GUI.Dialogs.AddLayers"/> 108 | </Unit> 109 | <Unit> 110 | <Filename Value="dialogs/tildy.gui.dialogs.editareaname.pas"/> 111 | <IsPartOfProject Value="True"/> 112 | <ComponentName Value="fEditAreaName"/> 113 | <HasResources Value="True"/> 114 | <ResourceBaseClass Value="Form"/> 115 | <UnitName Value="Tildy.GUI.Dialogs.EditAreaName"/> 116 | </Unit> 117 | <Unit> 118 | <Filename Value="i18n/tildy.gui.i18n.strconsts.pas"/> 119 | <IsPartOfProject Value="True"/> 120 | <UnitName Value="Tildy.GUI.i18n.StrConsts"/> 121 | </Unit> 122 | <Unit> 123 | <Filename Value="i18n/tildy.gui.i18n.runtime.pas"/> 124 | <IsPartOfProject Value="True"/> 125 | <UnitName Value="Tildy.GUI.i18n.Runtime"/> 126 | </Unit> 127 | </Units> 128 | </ProjectOptions> 129 | <CompilerOptions> 130 | <Version Value="11"/> 131 | <Target> 132 | <Filename Value="../../bin/$(TargetOS)/debug/tildy_gui"/> 133 | </Target> 134 | <SearchPaths> 135 | <IncludeFiles Value="$(ProjOutDir)"/> 136 | <OtherUnitFiles Value="dialogs;forms;brand;i18n"/> 137 | <UnitOutputDirectory Value="../../build/$(TargetOS)/debug"/> 138 | </SearchPaths> 139 | <Parsing> 140 | <SyntaxOptions> 141 | <IncludeAssertionCode Value="True"/> 142 | </SyntaxOptions> 143 | </Parsing> 144 | <CodeGeneration> 145 | <Checks> 146 | <IOChecks Value="True"/> 147 | <RangeChecks Value="True"/> 148 | <OverflowChecks Value="True"/> 149 | <StackChecks Value="True"/> 150 | </Checks> 151 | <VerifyObjMethodCallValidity Value="True"/> 152 | </CodeGeneration> 153 | <Linking> 154 | <Debugging> 155 | <DebugInfoType Value="dsDwarf3"/> 156 | <UseHeaptrc Value="True"/> 157 | <TrashVariables Value="True"/> 158 | </Debugging> 159 | <Options> 160 | <Win32> 161 | <GraphicApplication Value="True"/> 162 | </Win32> 163 | </Options> 164 | </Linking> 165 | </CompilerOptions> 166 | <Debugging> 167 | <Exceptions> 168 | <Item> 169 | <Name Value="EAbort"/> 170 | </Item> 171 | <Item> 172 | <Name Value="ECodetoolError"/> 173 | </Item> 174 | <Item> 175 | <Name Value="EFOpenError"/> 176 | </Item> 177 | </Exceptions> 178 | </Debugging> 179 | </CONFIG> 180 | -------------------------------------------------------------------------------- /docs/RESTRICTIONS_RU.md: -------------------------------------------------------------------------------- 1 | <div align="right"> 2 | 🇬🇧 <a href="./RESTRICTIONS.md">English</a> 3 | | 4 | 🇷🇺 Русский 5 | </div> 6 | 7 | # Ограничения 8 | 9 | ## OpenStreetMap 10 | 11 | При загрузке плиток с серверов, принадлежащих OpenStreetMap, вы должны знать ограничения. Официально массовая загрузка плиток (по мои наблюдениям это более чем 1000) запрещена. Так, сервер ограничивает скорость скачивания и количество отдаваемых плиток. **В случае злоупотребления вас могут забанить, будьте осторожны!** Это связано с тем, что сервера работают за счёт скромных пожертвований, которых не хватает на сервера, мощность которых позволила бы не накладывать ограничения. 12 | 13 | > Подробнее. Tile Usage Policy - https://operations.osmfoundation.org/policies/tiles/ 14 | 15 | В качестве решения этих проблем, рекомендуется использовать свой локальный сервер, который будет генерировать плитки, аналогичные OSM. 16 | 17 | ## Решение. Локальный сервер OSM 18 | 19 | Список используемых источников: 20 | - *openstreetmap-tile-server* - https://github.com/Overv/openstreetmap-tile-server 21 | - *Using OpenStreetMap Offline* - https://www.gibbard.me/openstreetmap/ 22 | - *switch2osm* - https://switch2osm.org/ 23 | 24 | Эта инструкция описывает то, как это делаю я. 25 | 26 | ### Подготовка Docker 27 | 28 | #### Установка 29 | 30 | Alt Linux p11: 31 | ```bash 32 | sudo apt-get install docker-engine 33 | ``` 34 | 35 | [Для других дистрибутивов](https://docs.docker.com/engine/install/) 36 | 37 | #### Запуск 38 | 39 | Dcoker работает как сервис, поэтому для его автоматического старта при каждом запуске нужно включить автозапуск. Так же мы сразу его запустим: 40 | 41 | ```bash 42 | systemctl enable --now docker 43 | ``` 44 | 45 | Проверяем запустился ли Docker: 46 | 47 | ```bash 48 | systemctl status docker 49 | ``` 50 | 51 | Должно быть так: 52 | 53 | ```bash 54 | ● docker.service - Docker Application Container Engine 55 | Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled) 56 | Active: active (running) since Tue 2024-11-19 13:56:58 +05; 2 days ago 57 | TriggeredBy: ● docker.socket 58 | ``` 59 | 60 | #### Создание volume для данных 61 | 62 | *Volume* в Docker имитирует работу разделов. Для работы нашего сервера достаточно создать одного *volume*: 63 | 64 | ```bash 65 | docker volume create osm-data 66 | ``` 67 | 68 | Проверяем создался ли *volume*: 69 | 70 | ```bash 71 | docker volume ls 72 | ``` 73 | 74 | Должно быть так: 75 | 76 | ```bash 77 | DRIVER VOLUME NAME 78 | local osm-data 79 | ``` 80 | 81 | 82 | ### Картографические данные 83 | 84 | Дальше необходимо скачать картографические данные в формате PDF, на основе которых будет заполняться база данных внутри контейнера. Официальные PBF от OpenStreetMap можно скачать с сайта [download.geofabrik.de](https://download.geofabrik.de/). Для примера я возьму [данные Российской Федерации](https://download.geofabrik.de/russia-latest.osm.pbf). 85 | 86 | 87 | ### Импорт данных в СУБД контейнера 88 | 89 | > Будьте готовы, что понадобится немало места. Всё зависит от того, какого объёма у вас PBF. 90 | 91 | На основе PBF сервер будет заполнять базу данных. Тут нужно определиться, где они должны будут находиться. Можно заполнять внутри *osm_data* (*volume*, который мы создали выше), которая хранится в системном каталоге вместе с docker. Тогда команда будет выглядеть так: 92 | 93 | ```bash 94 | docker run \ 95 | -v /absolute/path/to/russia-latest.osm.pbf:/data/region.osm.pbf \ 96 | -v osm-data:/data/database/ \ 97 | overv/openstreetmap-tile-server \ 98 | import 99 | ``` 100 | 101 | Я же буду сохранять в домашнюю директорию, так как там больше места: 102 | 103 | ```bash 104 | docker run \ 105 | -v /home/kirill/osm_pbf/russia-latest.osm.pbf:/data/region.osm.pbf \ 106 | -v /home/kirill/osm_database/:/data/database/ \ 107 | overv/openstreetmap-tile-server \ 108 | import 109 | ``` 110 | 111 | Убедитесь, что пути указаны верно, в особенности к PBF, так как если docker не найдёт по указанному пути файл, контейнер загрузит PBF Люксембурга для примера. 112 | 113 | Дальше у вас запустится процесс импорта данных. Если всё пройдёт успешно, в консоли выведется: 114 | 115 | ```bash 116 | exit 0 117 | ``` 118 | 119 | ### Запуск сервера 120 | 121 | Для запуска нужно слегка модифицировать команду для импорта, добавив опцию для проброса портов контейнера на системные, и изменить команду import на run. В первом варианте она будет выглядеть так: 122 | 123 | ```bash 124 | docker run \ 125 | -p 8080:80 \ 126 | -v osm-data:/data/database/ \ 127 | -d overv/openstreetmap-tile-server \ 128 | run 129 | ``` 130 | 131 | В варианте с сохранением в домашнюю директорию будет так: 132 | 133 | ```bash 134 | docker run \ 135 | -p 8080:80 \ 136 | -v /home/kirill/osm_database/:/data/database/ \ 137 | -d overv/openstreetmap-tile-server \ 138 | run 139 | ``` 140 | 141 | Посмотреть список активных контейнеров: 142 | 143 | ```bash 144 | docker container ls 145 | ``` 146 | 147 | или 148 | 149 | ```bash 150 | docker ps 151 | ``` 152 | 153 | Должно быть так: 154 | 155 | ```bash 156 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 157 | 26cce2b17e6b overv/openstreetmap-tile-server "/run.sh run" 22 hours ago Up 22 hours 5432/tcp, 0.0.0.0:8080->80/tcp, :::8080->80/tcp thirsty_black 158 | ``` 159 | 160 | Ваш ```UPTIME``` должен быть меньше. 161 | 162 | Если вы не видите в списке свой контейнер, попробуйте посмотреть полный список: 163 | 164 | ```bash 165 | docker ps -a 166 | ``` 167 | 168 | Если в ```STATUS``` вы видите ```Exited (1)```, значит произошла ошибка. Подробнее об ошибке можно посмотреть через: 169 | 170 | ```bash 171 | docker logs <название контейнера> 172 | ``` 173 | 174 | Название контейнера указано в столбце ```NAMES```. 175 | 176 | ### Получение плиток 177 | 178 | После запуска контейнера, можно получать плитки по адресу: 179 | ``` 180 | http://localhost:8080/tile/{z}/{x}/{y}.png 181 | ``` 182 | 183 | Пробуем скачать нужные нам плитки: 184 | 185 | ```bash 186 | tildy/examples/custom_providers.sh 187 | ``` 188 | 189 | В первый раз это будет медленно, так как плитки будут генерироваться в реальном времени. Можно заранее их сгенерировать: 190 | 191 | Подключаемся к терминалу контейнера: 192 | 193 | ```bash 194 | docker exec -it thirsty_black /usr/bin/sh 195 | ``` 196 | 197 | Рендерим по уровню зума от 0 до 6 для всего мира, в 2 потока (-n 2): 198 | 199 | ```bash 200 | render_list --all -z 0 -Z 6 -n 2 201 | ``` 202 | 203 | ### Что можно улучшить 204 | 205 | Полный список всех опций для повышения производительности, таких как: настройка занимаемой памяти, используемых потоков и пр. смотреть в списке источников, что указаны в начале раздела с OSM. -------------------------------------------------------------------------------- /docs/RESTRICTIONS.md: -------------------------------------------------------------------------------- 1 | <div align="right"> 2 | 🇬🇧 English 3 | | 4 | 🇷🇺 <a href="./RESTRICTIONS_RU.md">Русский</a> 5 | </div> 6 | 7 | # RESTRICTIONS 8 | 9 | 🇬🇧 English | 🇷🇺 [Русский](./RESTRICTIONS_RU.md) 10 | 11 | ## OpenStreetMap 12 | 13 | When downloading tiles from servers owned by OpenStreetMap, you should be aware of the restrictions. Officially, the massive diwnload of tiles (according to my observations, it is more than 1000) is prohibited. So, the server restrict the download speed and the number of tiles to be given. **In case of abuse, you may be banned, be careful!** This is due to the fact that the servers operate at the expense of modest donations, which are not enough for servers whose capacity would allow them not to impose restrictions. 14 | 15 | > Detailed. Tile Usage Policy - https://operations.osmfoundation.org/policies/tiles/ 16 | 17 | As a solution to these problems, it is recommended to use your local server, which will generate tiles similar to OSM. 18 | 19 | ## Solution. Local OSM Server 20 | 21 | Used sources: 22 | - *openstreetmap-tile-server* - https://github.com/Overv/openstreetmap-tile-server 23 | - *Using OpenStreetMap Offline* - https://www.gibbard.me/openstreetmap/ 24 | - *switch2osm* - https://switch2osm.org/ 25 | 26 | This instruction describes how I do it. 27 | 28 | ### Prepare Docker 29 | 30 | #### Install 31 | 32 | Alt Linux p11: 33 | ```bash 34 | sudo apt-get install docker-engine 35 | ``` 36 | 37 | [Для других дистрибутивов](https://docs.docker.com/engine/install/) 38 | 39 | #### Lounch 40 | 41 | Docker works as a service, so to start it automatically, you need to enable autorun every time you start it. We will also launch it immediately: 42 | 43 | ```bash 44 | systemctl enable --now docker 45 | ``` 46 | 47 | Checking if Docker has started: 48 | 49 | ```bash 50 | systemctl status docker 51 | ``` 52 | 53 | Must be: 54 | 55 | ```bash 56 | ● docker.service - Docker Application Container Engine 57 | Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled) 58 | Active: active (running) since Tue 2024-11-19 13:56:58 +05; 2 days ago 59 | TriggeredBy: ● docker.socket 60 | ``` 61 | 62 | #### Create volume for data 63 | 64 | *Volume* in Docker simulates the operation of partitions. For our server to work, it is enough to create one *volume*: 65 | 66 | ```bash 67 | docker volume create osm-data 68 | ``` 69 | 70 | Check if the *volume* has been created: 71 | 72 | ```bash 73 | docker volume ls 74 | ``` 75 | 76 | Must be: 77 | 78 | ```bash 79 | DRIVER VOLUME NAME 80 | local osm-data 81 | ``` 82 | 83 | 84 | ### Cartographic data 85 | 86 | Next, you need to download the cartographic data in PDF format, on the basis of which the database inside the container will be filled in. The official PBF from OpenStreetMap can be downloaded from the website [download.geofabrik.de](https://download.geofabrik.de/). For example, I'll take [данные Российской Федерации](https://download.geofabrik.de/russia-latest.osm.pbf). 87 | 88 | 89 | ### Importing data into a container DBMS 90 | 91 | > Be prepared that you will need a lot of space. It all depends on how much PBF you have. 92 | 93 | Based on the PBF, the server will populate the database. Here it is necessary to determine where they will have to be. You can fill in the *osm_data* (*volume*, which we created above), which is stored in the system directory along with docker. Then the command will look like this: 94 | 95 | ```bash 96 | docker run \ 97 | -v /absolute/path/to/russia-latest.osm.pbf:/data/region.osm.pbf \ 98 | -v osm-data:/data/database/ \ 99 | overv/openstreetmap-tile-server \ 100 | import 101 | ``` 102 | 103 | I will save it to my home directory, since there is more space there: 104 | 105 | ```bash 106 | docker run \ 107 | -v /home/kirill/osm_pbf/russia-latest.osm.pbf:/data/region.osm.pbf \ 108 | -v /home/kirill/osm_database/:/data/database/ \ 109 | overv/openstreetmap-tile-server \ 110 | import 111 | ``` 112 | 113 | Make sure that the paths are specified correctly, especially to PBF, because if docker does not find a file on the specified path, the container will load PBF Luxembourg for example. 114 | 115 | Next, you will start the data import process. If everything goes well, the console will display: 116 | 117 | ```bash 118 | exit 0 119 | ``` 120 | 121 | ### Starting the server 122 | 123 | To run, you need to slightly modify the import command by adding an option to forward the container ports to the system ports, and change the import command to run. In the first version, it will look like this: 124 | 125 | ```bash 126 | docker run \ 127 | -p 8080:80 \ 128 | -v osm-data:/data/database/ \ 129 | -d overv/openstreetmap-tile-server \ 130 | run 131 | ``` 132 | 133 | In the case of saving to the home directory, it will be like this: 134 | 135 | ```bash 136 | docker run \ 137 | -p 8080:80 \ 138 | -v /home/kirill/osm_database/:/data/database/ \ 139 | -d overv/openstreetmap-tile-server \ 140 | run 141 | ``` 142 | 143 | View the list of active containers: 144 | 145 | ```bash 146 | docker container ls 147 | ``` 148 | 149 | or 150 | 151 | ```bash 152 | docker ps 153 | ``` 154 | 155 | Must be: 156 | 157 | ```bash 158 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 159 | 26cce2b17e6b overv/openstreetmap-tile-server "/run.sh run" 22 hours ago Up 22 hours 5432/tcp, 0.0.0.0:8080->80/tcp, :::8080->80/tcp thirsty_black 160 | ``` 161 | 162 | Your ```UPTIME``` must be smaller. 163 | 164 | If you don't see your container in the list, try to see the full list: 165 | 166 | ```bash 167 | docker ps -a 168 | ``` 169 | 170 | If you see ```Exited (1)``` in ```STATUS```, then an error has occurred. More information about the error can be viewed via: 171 | 172 | ```bash 173 | docker logs <container name> 174 | ``` 175 | 176 | The name of the container is indicated in the column ```NAMES```. 177 | 178 | ### Download tiles 179 | 180 | After launching the container, you can receive tiles at:: 181 | 182 | ``` 183 | http://localhost:8080/tile/{z}/{x}/{y}.png 184 | ``` 185 | 186 | Trying to download the tiles we need: 187 | 188 | ```bash 189 | tildy/examples/custom_providers.sh 190 | ``` 191 | 192 | It will be slow the first time, as the tiles will be generated in real time. You can generate them in advance: 193 | 194 | Connecting to the container terminal: 195 | 196 | ```bash 197 | docker exec -it thirsty_black /usr/bin/sh 198 | ``` 199 | 200 | Rendering by zoom level from 0 to 6 for the whole world, in 2 streams (-n 2): 201 | 202 | ```bash 203 | render_list --all -z 0 -Z 6 -n 2 204 | ``` 205 | 206 | ### What can be improved 207 | 208 | For a complete list of all options to improve performance, such as: configuring memory usage, threads used, etc., see the list of sources that are indicated at the beginning of the OSM section. -------------------------------------------------------------------------------- /docs/USAGE.md: -------------------------------------------------------------------------------- 1 | <div align="right"> 2 | 🇬🇧 English 3 | | 4 | 🇷🇺 <a href="./USAGE_RU.md">Русский</a> 5 | </div> 6 | 7 | # USAGE 8 | 9 | ## Synopsis 10 | ./tildy **[OPTION]** **[PARAMETER]** ... 11 | 12 | 13 | ## Syntax 14 | 15 | ``` 16 | ./tildy -provider osm-standard ... 17 | ``` 18 | ``` 19 | ./tildy --provider=osm-standard ... 20 | ``` 21 | 22 | ## Options 23 | 24 | ### provider, p [String] 25 | 26 | Choosing the provider that will provide the tiles. List of prepared providers: 27 | 28 | * `osm-standard` - OpenStreetMap Standard 29 | * `railway-standard` - OpenRailwayMap Standard 30 | * `railway-maxspeed` - OpenRailwayMap Maxspeed 31 | * `railway-electrification` - OpenRailwayMap Electrification 32 | 33 | Example: 34 | 35 | ``` 36 | ... -provider osm-standard 37 | ``` 38 | ``` 39 | ... -p osm-standard 40 | ``` 41 | 42 | ### providers, ps [String] 43 | 44 | You can import your providers via the configuration file. Each provider starts with `[Provider]`. 45 | 46 | Fields used: 47 | 48 | * `identifier` - the identifier to be used in layers or in *provider* 49 | * `name` - the name of the provider, which will then be used when substituting macros in *out* 50 | * `url` - a link with macros that will be used to get tiles. 51 | * `cache` - identical to the *cache* option, the path to the folder with cached tiles 52 | * `use-cache-only` - if ``yes`` is specified, then it will work only with the cache, in other cases it will not. by default, it works not only with the cache 53 | 54 | Example of a configuration file `custom_providers.ini`: 55 | 56 | ```ini 57 | [Provider] 58 | ident=osm-standard-local 59 | name=OpenStreetMap-Mapnik 60 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 61 | ``` 62 | 63 | Example of a provider that takes tiles only from the cache: 64 | ```ini 65 | [Provider] 66 | ident=osm-standard-cache 67 | name=OpenStreetMap-Standard 68 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 69 | cache=путь/до/папки/с/кешем/ 70 | # by default, "no" is used. use this option only if you need to work with the cache only. 71 | use-cache-only=yes 72 | ``` 73 | 74 | Example: 75 | ``` 76 | ... -providers custom_providers.ini -provider osm-standard-local 77 | ``` 78 | ``` 79 | ... -ps custom_providers.ini -p osm-standard-local 80 | ``` 81 | 82 | 83 | **** 84 | 85 | ### layers, ls [String] 86 | 87 | Using a configuration file with layers. The layers are tiles from selected providers. All the layers are superimposed on each other, eventually forming one single one. The layers are described in order from the bottom to the top. Each layer starts with `[Layer]`. 88 | 89 | Fields used: 90 | 91 | * `provider` - the provider used 92 | * `filter` - the filter used 93 | * `opacity` - opacity from 0 to 255, maximum by default 94 | 95 | Example of a configuration file `layers.ini`: 96 | 97 | ```ini 98 | [Layer] 99 | provider=osm-standard 100 | filter=grayscale 101 | 102 | [Layer] 103 | provider=railway-standard 104 | ``` 105 | 106 | Example: 107 | ``` 108 | .. -layers layers.ini 109 | ``` 110 | ``` 111 | .. -ls layers.ini 112 | ``` 113 | 114 | **** 115 | 116 | ### areas, as [String] 117 | 118 | Using a file with the specified areas for download. Each area starts with `[Area]`. 119 | 120 | Fields used: 121 | 122 | * `left` - left boundary (minimum longitude) 123 | * `top` - upper boundary (maximum latitude) 124 | * `right` - right border (maximum longitude) 125 | * `bottom` - lower border (minimum latitude) 126 | 127 | Example of a configuration file `areas.ini`: 128 | 129 | ```ini 130 | # Moscow 131 | [Area] 132 | left=37.1 133 | top=56 134 | right=38 135 | bottom=55.49 136 | 137 | # Yekaterinburg 138 | [Area] 139 | left=60 140 | top=57 141 | right=61 142 | bottom=56.6 143 | ``` 144 | 145 | Example: 146 | ``` 147 | .. -areas areas.ini 148 | ``` 149 | ``` 150 | .. -as areas.ini 151 | ``` 152 | 153 | **** 154 | 155 | ### monochrome, m [String] 156 | 157 | The color of monochroms tiles that should not be saved. Available variations: 158 | 159 | * `#FFFFFF` 160 | * `rgb(255, 255, 255)` 161 | * `rgba(255, 255, 255, 255)` 162 | 163 | Example: 164 | ``` 165 | .. -monochrome #FFFFFF 166 | ``` 167 | ``` 168 | .. -m #FFFFFF 169 | ``` 170 | 171 | **** 172 | 173 | ### monochromes, ms [String] 174 | 175 | Using a file with specified colors of monotonous tiles that should not be saved. Each color starts with `[Monochrome]`. 176 | 177 | Fields used: 178 | 179 | * `color` - color in the following variations: 180 | - `#FFFFFF` 181 | - `rgb(255, 255, 255)` 182 | - `rgba(255, 255, 255, 255)` 183 | 184 | Example of a configuration file `monochromes.ini`: 185 | 186 | ```ini 187 | [Monochrome] 188 | color=#C9C9C9 189 | 190 | [Monochrome] 191 | color=rgb(255, 255, 255) 192 | 193 | [Monochrome] 194 | color=rgba(0, 0, 0, 255) 195 | ``` 196 | 197 | Example: 198 | ``` 199 | .. -monochromes monochromes.ini 200 | ``` 201 | ``` 202 | .. -ms monochromes.ini 203 | ``` 204 | 205 | **** 206 | 207 | ### out, o [String] 208 | 209 | The absolute or relative path for program output. You can use macros that will be replaced with real values when saved. If there are no folders in the path, they will be created. 210 | 211 | Macros: 212 | 213 | * `{p}` - provider name 214 | * `{z}` - zoom 215 | * `{x}` - X of tile number 216 | * `{y}` - Y of tile number 217 | 218 | Example: 219 | ``` 220 | .. -out tiles/{p}/{z}/{x}_{y} 221 | ``` 222 | ``` 223 | .. -o tiles/{p}/{z}/{x}_{y} 224 | ``` 225 | 226 | Default: 227 | ``` 228 | tiles/{p}/{z}/{x}/{y} 229 | ``` 230 | 231 | **** 232 | 233 | ### cache, c [String] 234 | 235 | The path to the folder with the provider's tiles that have already been downloaded. When using this option, the utility, upon receiving the tile, will check whether there is a tile in the cache folder: if so, the file from the disk will be used, if not, the server will be contacted for the tile. Macros can be used. 236 | 237 | Macros: 238 | 239 | * `{p}` - provider name 240 | * `{z}` - zoom 241 | * `{x}` - X of tile number 242 | * `{y}` - Y of tile number 243 | 244 | Example: 245 | ``` 246 | .. -cache tiles/{p}/{z}/{x}_{y} 247 | ``` 248 | ``` 249 | .. -c tiles/{p}/{z}/{x}_{y} 250 | ``` 251 | 252 | **** 253 | 254 | ### use-cache-only, uco 255 | 256 | When using this option, the utility will only work with cached tiles, the path to which is indicated via the *cache* option, without using the Internet. 257 | 258 | **** 259 | 260 | ### min-zoom, z [Unsigned Integer] 261 | 262 | > Required option 263 | 264 | The lower limit of the zoom. 265 | 266 | Example: 267 | ``` 268 | ... -min-zoom 6 269 | ``` 270 | ``` 271 | ... -z 6 272 | ``` 273 | 274 | ### max-zoom, Z [Unsigned Integer] 275 | 276 | > Required option 277 | 278 | The higher limit of the zoom. 279 | 280 | Example: 281 | ``` 282 | ... -max-zoom 7 283 | ``` 284 | ``` 285 | ... -Z 7 286 | ``` 287 | 288 | **** 289 | 290 | **Important!** In order for negative coordinate values to be taken into account, they must be specified using the following syntax: 291 | ``` 292 | ... --left=-56.674619 293 | ``` 294 | 295 | ### left, l [Double] 296 | 297 | The left border of the selected area (minimum longitude). 298 | 299 | Example: 300 | ``` 301 | ... -left 57.02137767 302 | ``` 303 | ``` 304 | ... -l 57.02137767 305 | ``` 306 | 307 | 308 | ### top, t [Double] 309 | 310 | The upper limit of the selected area (maximum latitude). 311 | 312 | Example: 313 | ``` 314 | ... -top 120 315 | ``` 316 | ``` 317 | ... -t 120 318 | ``` 319 | 320 | 321 | ### right, r [Double] 322 | 323 | The right border of the selected area (maximum longitude). 324 | 325 | Example: 326 | ``` 327 | ... --bottom=143.1 328 | ``` 329 | ``` 330 | ... -b 143.1 331 | ``` 332 | 333 | ### bottom, b [Double] 334 | 335 | The lower boundary of the selected area (minimum latitude). 336 | 337 | Example: 338 | ``` 339 | ... -bottom 143.1 340 | ``` 341 | ``` 342 | ... -b 143.1 343 | ``` 344 | 345 | **** 346 | 347 | ### bbox, bb 348 | 349 | Setting the download area as in osmium. The following order is used: 350 | 351 | ``` 352 | ... -bb MinLon,MinLat,MaxLon,MaxLat 353 | ``` 354 | 355 | **** 356 | 357 | ### show-file-type, sft 358 | 359 | Enabling the display of the `.png` extension in the file name. The extension is always *PNG*. 360 | 361 | **** 362 | 363 | ### skip-existing, ske 364 | 365 | Skipping tiles that already exist on the disk when received from the server. 366 | 367 | **** 368 | 369 | ### skip-missing, skm 370 | 371 | Skipping missing tiles when received from the server. 372 | 373 | **** 374 | 375 | ### filter, f [String] 376 | 377 | Applying a filter to tiles from the *provider*. List of prepared filters: 378 | 379 | * `grayscale` - grayscale 380 | 381 | Example: 382 | ``` 383 | ... -filter grayscale 384 | ``` 385 | ``` 386 | ... -f grayscale 387 | ``` 388 | 389 | **** 390 | 391 | ### tile-res, res [Unsigned Integer] 392 | 393 | The resolution of the saved images. Use it if you are not satisfied with the original resolution. 394 | 395 | Example: 396 | ``` 397 | ./tildy -p railway-standard -z 0 -Z 2 -tile-res 256 398 | ``` 399 | ``` 400 | ./tildy -p railway-standard -z 0 -Z 2 -res 256 401 | ``` 402 | 403 | ### version, v 404 | 405 | Print program version in format: 406 | 407 | ``` 408 | tildy [major].[minor] 409 | ``` 410 | 411 | ### help, h 412 | 413 | Show help. 414 | 415 | Example: 416 | ``` 417 | ... -h 418 | ``` 419 | ``` 420 | ... -help 421 | ``` 422 | -------------------------------------------------------------------------------- /docs/USAGE_RU.md: -------------------------------------------------------------------------------- 1 | <div align="right"> 2 | 🇬🇧 <a href="./USAGE.md">English</a> 3 | | 4 | 🇷🇺 Русский 5 | </div> 6 | 7 | # Использование 8 | 9 | ## Синопсис 10 | ./tildy **[ОПЦИЯ]** **[ПАРАМЕТР]** ... 11 | 12 | 13 | ## Синтаксис 14 | 15 | ``` 16 | ./tildy -provider osm-standard ... 17 | ``` 18 | ``` 19 | ./tildy --provider=osm-standard ... 20 | ``` 21 | 22 | ## Опции 23 | 24 | ### provider, p [String] 25 | 26 | Выбор провайдера, который будет предоставлять плитки. Список подготовленных провайдеров: 27 | 28 | * `osm-standard` - OpenStreetMap Standard 29 | * `railway-standard` - OpenRailwayMap Standard 30 | * `railway-maxspeed` - OpenRailwayMap Maxspeed 31 | * `railway-electrification` - OpenRailwayMap Electrification 32 | 33 | Пример: 34 | ``` 35 | ... -provider osm-standard 36 | ``` 37 | ``` 38 | ... -p osm-standard 39 | ``` 40 | 41 | **** 42 | 43 | ### providers, ps [String] 44 | 45 | Вы можете импортировать своих провайдеров, через конфигурационный файл. Каждый провайдер начинается с ``[Provider]``. 46 | 47 | Используемые поля: 48 | 49 | * `ident` - идентификатор, который будет использоваться в слоях или в `provider` 50 | * `name` - название провайдера, которое будет потом использоваться при подстановки макросов в *out* 51 | * `url` - ссылка с макросами, по которой будут получаться плитки 52 | * `cache` - идентично опции *cache*, путь до папки с кэшированными плитками 53 | * `use-cache-only` - если указано ``yes``, то будет работа только с кешем, в остальных случаях не будет. по умолчанию работа не только с кешем 54 | 55 | Пример конфигурационного файла `custom_providers.ini`: 56 | 57 | ```ini 58 | [Provider] 59 | ident=osm-standard-local 60 | name=OpenStreetMap-Mapnik 61 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 62 | ``` 63 | 64 | Пример провайдера, который берёт плитки только из кеша: 65 | ```ini 66 | [Provider] 67 | ident=osm-standard-cache 68 | name=OpenStreetMap-Standard 69 | url=http://localhost:8080/tile/{z}/{x}/{y}.png 70 | cache=путь/до/папки/с/кешем/ 71 | # по умолчанию используется "no". используете опцию, только если нужна работа только с кэшем 72 | use-cache-only=yes 73 | ``` 74 | 75 | Пример: 76 | ``` 77 | ... -providers custom_providers.ini -provider osm-standard-local 78 | ``` 79 | ``` 80 | ... -ps custom_providers.ini -p osm-standard-local 81 | ``` 82 | 83 | 84 | **** 85 | 86 | ### layers, ls [String] 87 | 88 | Использование файла с конфигурацией со слоями. Слои представляют собой плитки от выбранных провайдеров. Все слои накладываются друг на друга по итогу формируя одну единую. Описание слоёв идёт по порядку с нижнего к верхнему. Каждый слой начинается с ``[Layer]``. 89 | 90 | Используемые поля: 91 | 92 | * `provider` - используемый провайдер 93 | * `filter` - используемый фильтр 94 | * `opacity` - непрозрачность от 0 до 255, по умолчанию максимальная 95 | 96 | Пример конфигурационного файла `layers.ini`: 97 | 98 | ```ini 99 | [Layer] 100 | provider=osm-standard 101 | filter=grayscale 102 | 103 | [Layer] 104 | provider=railway-standard 105 | ``` 106 | 107 | Пример: 108 | ``` 109 | .. -layers layers.ini 110 | ``` 111 | ``` 112 | .. -ls layers.ini 113 | ``` 114 | 115 | **** 116 | 117 | ### areas, as [String] 118 | 119 | Использование файла с укзанными областями для скачивания. Каждая область начинается с ``[Area]``. 120 | 121 | Используемые поля: 122 | 123 | * `left` - левая граница (минимальная долгота) 124 | * `top` - верхняя граница (максимальная широта) 125 | * `right` - правая граница (максимальная долгота) 126 | * `bottom` - нижняя граница (минимальная широта) 127 | 128 | Пример конфигурационного файла `areas.ini`: 129 | 130 | ```ini 131 | # Moscow 132 | [Area] 133 | left=37.1 134 | top=56 135 | right=38 136 | bottom=55.49 137 | 138 | # Yekaterinburg 139 | [Area] 140 | left=60 141 | top=57 142 | right=61 143 | bottom=56.6 144 | ``` 145 | 146 | Пример: 147 | ``` 148 | .. -areas areas.ini 149 | ``` 150 | ``` 151 | .. -as areas.ini 152 | ``` 153 | 154 | **** 155 | 156 | ### monochrome, m [String] 157 | 158 | Цвет монотонных плиток, которые не должны сохраняться. Доступные вариации: 159 | 160 | * `#FFFFFF` 161 | * `rgb(255, 255, 255)` 162 | * `rgba(255, 255, 255, 255)` 163 | 164 | Пример: 165 | ``` 166 | .. -monochrome #FFFFFF 167 | ``` 168 | ``` 169 | .. -m #FFFFFF 170 | ``` 171 | 172 | **** 173 | 174 | ### monochromes, ms [String] 175 | 176 | Использование файла с указанными цветами монотонных плиток, которые не должны сохраняться. Каждый цвет начинается с ``[Monochrome]``. 177 | 178 | Используемые поля: 179 | 180 | * `color` - цвет в следующий вариациях: 181 | - `#FFFFFF` 182 | - `rgb(255, 255, 255)` 183 | - `rgba(255, 255, 255, 255)` 184 | 185 | Пример конфигурационного файла `monochromes.ini`: 186 | 187 | ```ini 188 | [Monochrome] 189 | color=#C9C9C9 190 | 191 | [Monochrome] 192 | color=rgb(255, 255, 255) 193 | 194 | [Monochrome] 195 | color=rgba(0, 0, 0, 255) 196 | ``` 197 | 198 | Пример: 199 | ``` 200 | .. -monochromes monochromes.ini 201 | ``` 202 | ``` 203 | .. -ms monochromes.ini 204 | ``` 205 | 206 | **** 207 | 208 | ### out, o [String] 209 | 210 | Абсолютный или относительный путь для вывода программы. Можно использовать макросы, которые при сохранении будут подменятся на реальные значения. Если папки в пути отсутствуют, они будут созданы. 211 | 212 | Макросы: 213 | 214 | * `{p}` - Имя провайдера 215 | * `{z}` - Уровень зума 216 | * `{x}` - X номера плитки 217 | * `{y}` - Y номера плитки 218 | 219 | Пример: 220 | ``` 221 | .. -out tiles/{p}/{z}/{x}_{y} 222 | ``` 223 | ``` 224 | .. -o tiles/{p}/{z}/{x}_{y} 225 | ``` 226 | 227 | По умолчанию: 228 | ``` 229 | tiles/{p}/{z}/{x}/{y} 230 | ``` 231 | 232 | **** 233 | 234 | ### cache, c [String] 235 | 236 | Путь к папке с уже скачанными плитками провайдера. При использовании данной опции, утилита, при получении плитки, будет проверять, есть ли плитка в папке с кешем: если да, то будет использован файл с диска, если нет, то будет обращение к серверу за плиткой. Можно использовать макросы. 237 | 238 | Макросы: 239 | 240 | * `{p}` - Имя провайдера 241 | * `{z}` - Уровень зума 242 | * `{x}` - X номера плитки 243 | * `{y}` - Y номера плитки 244 | 245 | Пример: 246 | ``` 247 | .. -cache tiles/{p}/{z}/{x}_{y} 248 | ``` 249 | ``` 250 | .. -c tiles/{p}/{z}/{x}_{y} 251 | ``` 252 | 253 | **** 254 | 255 | ### use-cache-only, uco 256 | 257 | При использовании данной опции, утилита будет работать только с кэшированными плитками, путь до которых указывается через опцию *cache*, без использования интернета. 258 | 259 | **** 260 | 261 | ### min-zoom, z [Unsigned Integer] 262 | 263 | > Обязательный параметр 264 | 265 | Нижняя граница зума. 266 | 267 | Пример: 268 | ``` 269 | ... -min-zoom 6 270 | ``` 271 | ``` 272 | ... -z 6 273 | ``` 274 | 275 | ### max-zoom, Z [Unsigned Integer] 276 | 277 | > Обязательный параметр 278 | 279 | Верхняя граница зума. 280 | 281 | Пример: 282 | ``` 283 | ... -Z 7 284 | ``` 285 | 286 | **** 287 | 288 | **Важно!** Чтобы отрицательные значения координат учитывались, их необходимо указывать через следующий синтаксис: 289 | ``` 290 | ... --left=-56.674619 291 | ``` 292 | 293 | ### left, l [Double] 294 | 295 | Левая граница выбранной области (минимальная долгота). 296 | 297 | Пример: 298 | ``` 299 | ... -left 57.02137767 300 | ``` 301 | ``` 302 | ... -l 57.02137767 303 | ``` 304 | 305 | 306 | ### top, t [Double] 307 | 308 | Верхняя граница выбранной области (максимальная широта). 309 | 310 | Пример: 311 | ``` 312 | ... -top 120 313 | ``` 314 | ``` 315 | ... -t 120 316 | ``` 317 | 318 | 319 | ### right, r [Double] 320 | 321 | Правая граница выбранной области (максимальная долгота). 322 | 323 | Пример: 324 | ``` 325 | ... -right 42.7 326 | ``` 327 | ``` 328 | ... -r 42.7 329 | ``` 330 | 331 | ### bottom, b [Double] 332 | 333 | Нижняя граница выбранной области (минимальная широта). 334 | 335 | Пример: 336 | ``` 337 | ... -bottom 143.1 338 | ``` 339 | ``` 340 | ... -b 143.1 341 | ``` 342 | 343 | **** 344 | 345 | ### bbox, bb 346 | 347 | Задание области скачивания как в osmium. Используется следующая очерёдность: 348 | 349 | ``` 350 | ... -bb MinLon,MinLat,MaxLon,MaxLat 351 | ``` 352 | 353 | **** 354 | 355 | ### show-file-type, sft 356 | 357 | Включение отображение расширения `.png` в названии файла. Расширение всегда *PNG*. 358 | 359 | **** 360 | 361 | ### skip-existing, ske 362 | 363 | Пропуск плиток, которые уже присутствуют в директории на диске. 364 | 365 | **** 366 | 367 | ### skip-missing, skm 368 | 369 | Пропуск отсутствующих плиток при получении с сервера. 370 | 371 | **** 372 | 373 | ### filter, f [String] 374 | 375 | Наложение фильтра к плиткам от *provider*. Список подготовленных фильтров: 376 | 377 | * *grayscale* - тёмно-серый 378 | 379 | Пример: 380 | ``` 381 | ... -filter grayscale 382 | ``` 383 | ``` 384 | ... -f grayscale 385 | ``` 386 | 387 | **** 388 | 389 | ### tile-res, res [Unsigned Integer] 390 | 391 | Разрешение сохраняемых изображений. Используйте, если оригинальное разрешение вас не устраивает. 392 | 393 | Пример: 394 | ``` 395 | ./tildy -p railway-standard -z 0 -Z 2 -tile-res 256 396 | ``` 397 | ``` 398 | ./tildy -p railway-standard -z 0 -Z 2 -res 256 399 | ``` 400 | 401 | ### version, v 402 | 403 | Вывод версии программы в формате: 404 | 405 | ``` 406 | tildy [мажор].[минор] 407 | ``` 408 | 409 | ### help, h 410 | 411 | Вывод помощи. 412 | 413 | Пример: 414 | ``` 415 | ... -h 416 | ``` 417 | ``` 418 | ... -help 419 | ``` 420 | -------------------------------------------------------------------------------- /docs/media/logo.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 | 4 | <svg 5 | inkscape:export-ydpi="96" 6 | inkscape:export-xdpi="96" 7 | inkscape:export-filename="logo_variants_12-11-2024.png" 8 | sodipodi:docname="logo_12-11-2024.svg" 9 | inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" 10 | id="svg1" 11 | version="1.1" 12 | viewBox="0 0 512 512" 13 | height="512" 14 | width="512" 15 | xml:space="preserve" 16 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 17 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 18 | xmlns="http://www.w3.org/2000/svg" 19 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview 20 | id="namedview1" 21 | pagecolor="#ffffff" 22 | bordercolor="#000000" 23 | borderopacity="0.25" 24 | inkscape:showpageshadow="2" 25 | inkscape:pageopacity="0.0" 26 | inkscape:pagecheckerboard="0" 27 | inkscape:deskcolor="#d1d1d1" 28 | inkscape:document-units="px" 29 | inkscape:zoom="1" 30 | inkscape:cx="935" 31 | inkscape:cy="352.5" 32 | inkscape:window-width="1854" 33 | inkscape:window-height="1011" 34 | inkscape:window-x="0" 35 | inkscape:window-y="0" 36 | inkscape:window-maximized="1" 37 | inkscape:current-layer="layer2" 38 | showgrid="false" /><defs 39 | id="defs1"><inkscape:path-effect 40 | effect="lattice2" 41 | gridpoint0="1396.2232,173.12828" 42 | gridpoint1="1546.2096,115.64247" 43 | gridpoint2="1396.2232,440.52771" 44 | gridpoint3="1552.7601,387.78745" 45 | gridpoint4="1435.3574,206.39627" 46 | gridpoint5="1525.2676,201.03605" 47 | gridpoint6="1435.3574,440.52771" 48 | gridpoint7="1513.6259,440.52771" 49 | gridpoint8x9="1474.4916,206.39627" 50 | gridpoint10x11="1474.4916,440.52771" 51 | gridpoint12="1396.2232,264.92913" 52 | gridpoint13="1552.7601,264.92913" 53 | gridpoint14="1396.2232,381.99485" 54 | gridpoint15="1552.7601,381.99485" 55 | gridpoint16="1435.3574,264.92913" 56 | gridpoint17="1513.6259,264.92913" 57 | gridpoint18="1435.3574,381.99485" 58 | gridpoint19="1513.6259,381.99485" 59 | gridpoint20x21="1474.4916,264.92913" 60 | gridpoint22x23="1474.4916,381.99485" 61 | gridpoint24x26="1396.2232,323.46199" 62 | gridpoint25x27="1552.7601,323.46199" 63 | gridpoint28x30="1435.3574,323.46199" 64 | gridpoint29x31="1513.6259,323.46199" 65 | gridpoint32x33x34x35="1474.4916,323.46199" 66 | id="path-effect6-1-5" 67 | is_visible="true" 68 | lpeversion="1" 69 | horizontal_mirror="false" 70 | vertical_mirror="false" 71 | perimetral="false" 72 | live_update="true" /><inkscape:path-effect 73 | effect="lattice2" 74 | gridpoint0="-1555.4078,262.35572" 75 | gridpoint1="-1525.2677,252.07779" 76 | gridpoint2="-1555.4078,299.86858" 77 | gridpoint3="-1525.2677,287.1555" 78 | gridpoint4="-1547.8728,252.07779" 79 | gridpoint5="-1532.8027,252.07779" 80 | gridpoint6="-1547.8728,287.1555" 81 | gridpoint7="-1532.8027,287.1555" 82 | gridpoint8x9="-1540.3378,252.07779" 83 | gridpoint10x11="-1540.3378,287.1555" 84 | gridpoint12="-1555.4078,260.84722" 85 | gridpoint13="-1525.2677,260.84722" 86 | gridpoint14="-1555.4078,278.38607" 87 | gridpoint15="-1525.2677,278.38607" 88 | gridpoint16="-1547.8728,260.84722" 89 | gridpoint17="-1532.8027,260.84722" 90 | gridpoint18="-1547.8728,278.38607" 91 | gridpoint19="-1532.8027,278.38607" 92 | gridpoint20x21="-1540.3378,260.84722" 93 | gridpoint22x23="-1540.3378,278.38607" 94 | gridpoint24x26="-1555.4078,269.61665" 95 | gridpoint25x27="-1525.2677,269.61665" 96 | gridpoint28x30="-1547.8728,269.61665" 97 | gridpoint29x31="-1532.8027,269.61665" 98 | gridpoint32x33x34x35="-1540.3378,269.61665" 99 | id="path-effect13-8-2" 100 | is_visible="true" 101 | lpeversion="1" 102 | horizontal_mirror="false" 103 | vertical_mirror="false" 104 | perimetral="false" 105 | live_update="true" /></defs><g 106 | inkscape:label="Слой 1" 107 | inkscape:groupmode="layer" 108 | id="layer1" 109 | transform="translate(1050.5327)"><g 110 | inkscape:groupmode="layer" 111 | id="layer2" 112 | inkscape:label="Map" 113 | transform="matrix(0.995892,0,0,0.995892,-1212.43,-66.1332)"><g 114 | id="g3-7-5" 115 | style="display:inline;stroke-width:1.00011;stroke-dasharray:none" 116 | transform="translate(-983.86044,-9.9977284)"><path 117 | style="fill:#3a8ce4;fill-opacity:1;stroke:none;stroke-width:1.00011;stroke-dasharray:none" 118 | d="M 1166.5177,151.80038 V 549.1307 l 95.9132,-33.33691 c 23.6916,-126.45518 25.3,-258.53264 0,-397.4609 z" 119 | id="path1-9-4" 120 | sodipodi:nodetypes="ccccc" /><path 121 | style="fill:#377ab4;fill-opacity:1;stroke:none;stroke-width:1.00011;stroke-dasharray:none" 122 | d="m 1358.3441,151.80032 c 18.3913,164.61429 5.5362,274.57105 0,397.33032 l -95.9132,-33.33689 V 118.33289 Z" 123 | id="path1-6-2-7" 124 | sodipodi:nodetypes="ccccc" /><path 125 | style="fill:#3a8ce4;fill-opacity:1;stroke:none;stroke-width:1.00011;stroke-dasharray:none" 126 | d="m 1358.3441,151.80032 v 397.33031 l 95.9134,-33.33691 c 38.308,-119.22789 39.4025,-251.33603 0,-397.4609 z" 127 | id="path1-60-0-4" 128 | sodipodi:nodetypes="ccccc" /><path 129 | style="fill:#3a8ce4;fill-opacity:1;stroke:none;stroke-width:1.00011;stroke-dasharray:none" 130 | d="m 1550.1707,151.80025 c -30.5651,135.95633 -16.0323,266.72948 0,397.33031 l 95.9134,-33.33689 v -397.4609 z" 131 | id="path1-60-8-2-4" 132 | sodipodi:nodetypes="ccccc" /><path 133 | style="fill:#377ab4;fill-opacity:1;stroke:none;stroke-width:1.00011;stroke-dasharray:none" 134 | d="m 1550.1707,151.80025 v 397.33033 l -95.9132,-33.33689 V 118.33282 Z" 135 | id="path1-6-6-3-3" /></g><path 136 | id="rect3-1-7-0" 137 | style="display:inline;fill:#ffffff;stroke-width:0" 138 | d="m 1396.2232,419.35854 c 24.4794,-8.32858 73.5796,-25.11577 98.059,-33.44434 32.6369,-11.11974 57.9593,-19.67161 57.9593,-19.67161 0.1829,7.19135 0.5186,21.54486 0.5186,21.54486 -52.1795,17.59201 -156.5369,52.74026 -156.5369,52.74026 z m 111.7261,-160.74688 c -11.7863,4.24454 -23.8191,8.61691 -38.9706,14.15012 -13.3414,4.87216 -24.0767,8.80251 -24.0767,8.80251 0.1731,-42.5062 0.4883,-127.51574 0.4883,-127.51574 11.6027,-4.52442 26.9922,-10.54076 40.7429,-15.86974 13.2293,-5.12691 22.8522,-8.79573 22.8522,-8.79573 -0.8406,104.84947 -0.1955,24.37911 -1.0361,129.22858 z m -32.4452,111.03514 c -7.2754,-6.96791 -15.0664,-14.32133 -24.0851,-22.65092 -8.0965,-7.47779 -14.6229,-13.38559 -14.6229,-13.38559 -9.0831,-8.25371 -15.2938,-13.82517 -24.377,-22.07888 -8.0739,-7.34638 -14.3727,-13.23807 -14.3727,-13.23807 14.6235,-5.27745 30.1172,-10.94092 48.7741,-17.77149 16.5256,-6.05026 29.6243,-10.83634 29.6243,-10.83634 14.3281,-5.21996 31.0658,-11.2989 47.0235,-16.96525 14.8516,-5.27362 25.2731,-8.83295 25.2731,-8.83295 -5.7622,11.48614 -12.6095,24.20503 -20.8363,38.66264 -7.4427,13.07954 -13.7494,23.63123 -13.7494,23.63123 -12.1512,20.50498 -25.9668,42.94838 -38.6516,63.46562 z" 139 | inkscape:path-effect="#path-effect6-1-5" 140 | inkscape:original-d="m 1396.2232,421.99226 h 156.5369 v 18.53545 h -156.5369 z m 110.5664,-105.12765 h -62.434 V 206.39627 h 62.434 z m -31.7024,85.59062 -38.5016,-42.94476 -38.5014,-42.94477 h 77.0029 77.003 l -38.5016,42.94477 z" 141 | transform="translate(-1054.8706,38.506587)" /><rect 142 | style="fill:#357ab6;fill-opacity:1;stroke-width:0" 143 | id="rect8-5-7" 144 | width="32.656342" 145 | height="80.810417" 146 | x="-503.05328" 147 | y="-341.26752" 148 | transform="scale(-1)" /><path 149 | style="fill:#357ab6;fill-opacity:1;stroke-width:0" 150 | id="rect11-9-8" 151 | width="30.140158" 152 | height="35.077713" 153 | x="-1555.4078" 154 | y="252.07779" 155 | transform="matrix(-1,0,0,1,-1054.8706,38.506587)" 156 | sodipodi:type="rect" 157 | inkscape:path-effect="#path-effect13-8-2" 158 | d="m -1555.4078,262.35572 c 10.0588,-3.4194 30.1401,-10.27793 30.1401,-10.27793 0.01,11.701 0,35.07771 0,35.07771 -10.0389,4.24443 -30.1401,12.71308 -30.1401,12.71308 0,-12.50429 0,-25.00857 0,-37.51286 z" /><path 159 | id="path7-2-6" 160 | inkscape:transform-center-x="-1.842486e-05" 161 | inkscape:transform-center-y="5.0027789" 162 | d="m 500.52943,319.61859 c -5.31,-5.117 -15.57,-15.004 -15.57,-15.004 -4.97,-4.781 -14.57,-14.03 -14.57,-14.03 l -1.544,17.8011 1.544,17.2759 c 4.75,-0.952 14.57,-2.92 14.57,-2.92 8.22,-1.648 7.09,-1.422 15.57,-3.123 z" 163 | style="fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0" 164 | sodipodi:nodetypes="ccccccc" /><path 165 | id="rect13-3-2-8" 166 | d="m 331.30906,429.62405 c 14.4069,4.56658 43.1746,13.64884 43.1746,13.64884 0.013,16.63798 0,49.85381 0,49.85381 -14.382,-5.07412 -43.1746,-15.26985 -43.1746,-15.26985 1e-4,-16.07761 1e-4,-32.1552 0,-48.2328 z" 167 | style="display:inline;fill:#357ab6;fill-opacity:1;stroke-width:0" /><path 168 | id="rect13-3-7-89-8" 169 | d="m 470.39703,392.97972 c 14.4069,4.56658 43.1746,13.64884 43.1746,13.64884 0.013,16.63798 0,49.85381 0,49.85381 -14.382,-5.07412 -43.1746,-15.26985 -43.1746,-15.26985 1e-4,-16.07761 1e-4,-32.1552 0,-48.2328 z" 170 | style="display:inline;fill:#357ab6;fill-opacity:1;stroke-width:0" /><path 171 | id="path13-73-4" 172 | style="fill:#e8e8e8;fill-opacity:1;stroke-width:0" 173 | inkscape:transform-center-x="0.0001893748" 174 | inkscape:transform-center-y="5.743016" 175 | d="m 374.47946,365.36959 -34.6,-31.563 34.6,-8.389 z" /><path 176 | style="fill:#e8e8e8;fill-opacity:1;stroke-width:1.00136" 177 | d="m 374.48946,467.87279 v -21.30533 l -33.131,-11.09824 v 21.05291 z" 178 | id="path1205-61-3" /><path 179 | style="fill:#e8e8e8;fill-opacity:1;stroke-width:1.01668" 180 | d="m 503.52803,447.28786 v -21.96214 l -33.131,-11.44041 v 21.70196 z" 181 | id="path1205-6-29-1" /></g></g></svg> 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/cli/tildy_cli.lpr: -------------------------------------------------------------------------------- 1 | { 2 | Copyright (c) 2024-2025 Kirill Filippenok 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. } 15 | 16 | program tildy_cli; 17 | 18 | {$mode objfpc}{$H+} 19 | 20 | uses 21 | {$IFDEF UNIX} 22 | cthreads, 23 | {$ENDIF} 24 | SysUtils, Classes, CustApp, IniFiles, fileinfo, Math, 25 | // CLI 26 | Tildy.CLI.Options, 27 | // Core 28 | Tildy.Core.Engine, Tildy.Core.Projections, Tildy.Core.Filters; 29 | 30 | var 31 | OptionParameter: array[TOptionKind] of String; 32 | glOptions: TOptions; 33 | 34 | type 35 | 36 | { ATildy } 37 | 38 | ATildy = class(TCustomApplication) 39 | type 40 | TAreaArray = array of TArea; 41 | strict private 42 | FProviders: TProviders; 43 | FFilters : TFilters; 44 | strict private 45 | procedure SetupProviders; 46 | procedure SetupFilters; 47 | procedure ParseParameters; 48 | procedure ParseBoundingBox(ABoundingBoxString: String; out ABottom, ATop, ALeft, ARight: Extended); 49 | procedure ProgressCLI(const AZoom, X, Y: QWord; const ACurrentCount, ATotalCount, AZoomCurrentCount, 50 | AZoomTotalCount: QWord; const AMilliSeconds: Int64; const ADownloadInfo: TDownloadInfo); 51 | protected 52 | procedure DoRun; override; 53 | public 54 | constructor Create(TheOwner: TComponent); override; 55 | destructor Destroy; override; 56 | public 57 | procedure WriteHelp; 58 | function ImportAreas(var AAreaArray: TAreaArray; AFilePath: String): Boolean; 59 | function ImportLayers(ATildyEngine: TTildyEngine; AFilePath: String): Boolean; 60 | function ImportMonochromes(ATildyEngine: TTildyEngine; AFilePath: String): Boolean; 61 | function ImportProviders(AFilePath: String): Boolean; 62 | public 63 | property Providers: TProviders read FProviders write FProviders; 64 | property Filters : TFilters read FFilters write FFilters; 65 | end; 66 | 67 | procedure ATildy.SetupProviders; 68 | begin 69 | Providers.Add('osm-standard', 'OpenStreetMap-Standard', 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png', TEPSG3857.Create); 70 | Providers.Add('railway-standard', 'OpenRailwayMap-Standard', 'http://b.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', TEPSG3857.Create); 71 | Providers.Add('railway-maxspeed', 'OpenRailwayMap-Maxspeed', 'http://b.tiles.openrailwaymap.org/maxspeed/{z}/{x}/{y}.png', TEPSG3857.Create); 72 | Providers.Add('railway-electrification' , 'OpenRailwayMap-Electrification', 'http://b.tiles.openrailwaymap.org/electrification/{z}/{x}/{y}.png', TEPSG3857.Create); 73 | end; 74 | 75 | procedure ATildy.SetupFilters; 76 | begin 77 | Filters.Add('grayscale', TFilterGrayscale.Create); 78 | end; 79 | 80 | procedure ATildy.ParseParameters; 81 | var 82 | Option: TOptionKind; 83 | begin 84 | for Option := Low(TOptionKind) to High(TOptionKind) do 85 | begin 86 | {$IFDEF DEBUG} 87 | write(Option.Name); 88 | {$ENDIF} 89 | if HasOption(Option.Name) then 90 | begin 91 | Include(glOptions, Option); 92 | {$IFDEF DEBUG} 93 | write(' finded, value = '); 94 | {$ENDIF} 95 | try 96 | OptionParameter[Option] := getOptionValue(Option.Name); 97 | finally end; 98 | {$IFDEF DEBUG} 99 | writeLn(OptionParameter[Option]); 100 | {$ENDIF} 101 | Continue; 102 | end 103 | else if HasOption(Option.Ident) then 104 | begin 105 | Include(glOptions, Option); 106 | {$IFDEF DEBUG} 107 | write(' finded, value = '); 108 | {$ENDIF} 109 | try 110 | OptionParameter[Option] := getOptionValue(Option.Ident); 111 | finally end; 112 | {$IFDEF DEBUG} 113 | writeLn(OptionParameter[Option]); 114 | {$ENDIF} 115 | end 116 | else 117 | {$IFDEF DEBUG} 118 | WriteLn; 119 | {$ENDIF} 120 | end; 121 | end; 122 | 123 | procedure ATildy.ParseBoundingBox(ABoundingBoxString: String; out ABottom, ATop, ALeft, ARight: Extended); 124 | const 125 | _Delim = ','; 126 | var 127 | StartPosition, EndPosition: Integer; 128 | begin 129 | // MinLon,MinLat,MaxLon,MaxLat -> ALeft,ABottom,ARight,ATop 130 | // ALeft 131 | StartPosition := 1; 132 | EndPosition := Pos(_Delim, ABoundingBoxString); 133 | ALeft := Copy(ABoundingBoxString, StartPosition, EndPosition - StartPosition).ToExtended; 134 | // ABottom 135 | StartPosition := EndPosition + 1; 136 | EndPosition := Pos(_Delim, ABoundingBoxString, StartPosition); 137 | ABottom := Copy(ABoundingBoxString, StartPosition, EndPosition - StartPosition).ToExtended; 138 | // ARight 139 | StartPosition := EndPosition + 1; 140 | EndPosition := Pos(_Delim, ABoundingBoxString, StartPosition); 141 | ARight := Copy(ABoundingBoxString, StartPosition, EndPosition - StartPosition).ToExtended; 142 | // ATop 143 | StartPosition := EndPosition + 1; 144 | EndPosition := Length(ABoundingBoxString) + 1; 145 | ATop := Copy(ABoundingBoxString, StartPosition, EndPosition - StartPosition).ToExtended; 146 | end; 147 | 148 | procedure ATildy.ProgressCLI(const AZoom, X, Y: QWord; const ACurrentCount, ATotalCount, AZoomCurrentCount, 149 | AZoomTotalCount: QWord; const AMilliSeconds: Int64; const ADownloadInfo: TDownloadInfo); 150 | const 151 | EmptyProgress: String = '░'; 152 | FillProgress: String = '█'; 153 | 154 | function getProgressString(AProcents: QWord): String; 155 | var 156 | LFillCount: Byte; 157 | i: Byte; 158 | begin 159 | Result := String.Empty; 160 | LFillCount := Floor(AProcents / 10); 161 | for i := 1 to LFillCount do 162 | Result := Result + FillProgress; 163 | for i := 1 to (10 - LFillCount) do 164 | Result := Result + EmptyProgress; 165 | end; 166 | 167 | 168 | var 169 | LTotalProcents, LZoomProcents: Byte; 170 | begin 171 | LTotalProcents := Floor((ACurrentCount / ATotalCount) * 100); 172 | LZoomProcents := Floor((AZoomCurrentCount / AZoomTotalCount) * 100); 173 | WriteLn( 174 | Format( 175 | 'Total: %s %d%% (%d/%d)', 176 | [getProgressString(LTotalProcents), LTotalProcents, ACurrentCount, ATotalCount] 177 | ) 178 | + ' | ' + 179 | Format( 180 | 'Zoom: %s %d%% (%d/%d)', 181 | [getProgressString(LZoomProcents), LZoomProcents, AZoomCurrentCount, AZoomTotalCount] 182 | 183 | ) 184 | + ' | ' + 185 | Format( 186 | '%d ms', 187 | [AMilliSeconds] 188 | 189 | ) 190 | ); 191 | end; 192 | 193 | procedure ATildy.DoRun; 194 | var 195 | TildyEngine: TTildyEngine = nil; 196 | LMinZoom, LMaxZoom: Byte; 197 | LLeft, LTop, LRight, LBottom: Extended; 198 | LUseArea: Boolean = False; 199 | LUseRects: Boolean = False; 200 | LUseMonochromes: Boolean = False; 201 | LAreaArray: TAreaArray; 202 | ia, LAreasCount: Integer; 203 | LProgramVersion: TProgramVersion; 204 | begin 205 | if hasOption(OptionName[okHelp]) or hasOption(OptionIdent[okHelp]) then 206 | begin 207 | writeHelp; 208 | Terminate; 209 | Exit; 210 | end; 211 | 212 | if hasOption(OptionName[okVersion]) or hasOption(OptionIdent[okVersion]) then 213 | begin 214 | GetProgramVersion(LProgramVersion); 215 | WriteLn(Format('tildy %d.%d', [LProgramVersion.Major, LProgramVersion.Minor])); 216 | Terminate; 217 | Exit; 218 | end; 219 | 220 | ParseParameters; 221 | TildyEngine := TTildyEngine.Create; 222 | 223 | try 224 | // -providers 225 | if okProviders in glOptions then 226 | if not ImportProviders(OptionParameter[okProviders]) then 227 | raise EOpProviders.Create('Error when processing providers.'); 228 | 229 | // -layers 230 | if okLayers in glOptions then 231 | begin 232 | if not ImportLayers(TildyEngine, OptionParameter[okLayers]) then 233 | raise EOpLayers.Create('Error when processing layers.'); 234 | end 235 | else 236 | begin 237 | // -provider 238 | if okProvider in glOptions then 239 | begin 240 | if not Providers.Contains(OptionParameter[okProvider]) then 241 | raise EOpProvider.Create('The specified provider was not found.'); 242 | TildyEngine.Layers.Add(Providers[OptionParameter[okProvider]]) 243 | end 244 | else 245 | raise EOpProvider.Create('The provider is not specified.'); 246 | 247 | // -filter 248 | if okFilter in glOptions then 249 | begin 250 | if not Filters.Contains(OptionParameter[okFilter]) then 251 | raise EOpFilter.Create('The specified filter was not found.'); 252 | TildyEngine.Layers[0].Filter := Filters[OptionParameter[okFilter]]; 253 | end; 254 | 255 | // -cache 256 | if okCache in glOptions then 257 | begin 258 | if OptionParameter[okCache].IsEmpty then 259 | raise EOpCache.Create('Cache path is empty.'); 260 | TildyEngine.Layers[0].Provider.CachePath := OptionParameter[okCache]; 261 | end; 262 | 263 | // -use-cache-only 264 | if okUseCacheOnly in glOptions then 265 | TildyEngine.Layers[0].Provider.UseCacheOnly := True; 266 | end; 267 | 268 | // -out 269 | if okOut in glOptions then 270 | TildyEngine.Path := OptionParameter[okOut]; 271 | 272 | // -min-zoom 273 | if okMinZoom in glOptions then 274 | begin 275 | try 276 | LMinZoom := OptionParameter[okMinZoom].ToInteger 277 | except 278 | on E: Exception do 279 | raise EOpMinZoom.Create(E.Message); 280 | end; 281 | end 282 | else 283 | raise EOpMinZoom.Create('The required "-min-zoom" option is missing.'); 284 | 285 | // -max-zoom 286 | if okMaxZoom in glOptions then 287 | begin 288 | try 289 | LMaxZoom := OptionParameter[okMaxZoom].ToInteger; 290 | except 291 | on E: Exception do 292 | raise EOpMaxZoom.Create(E.Message); 293 | end; 294 | end 295 | else 296 | raise EOpMaxZoom.Create('The required "-max-zoom" option is missing.'); 297 | 298 | // -left, -top, -right, -bottom 299 | LUseArea := (okLeft in glOptions) 300 | or (okTop in glOptions) 301 | or (okRight in glOptions) 302 | or (okBottom in glOptions); 303 | 304 | // -left 305 | if okLeft in glOptions then 306 | begin 307 | try 308 | LLeft := OptionParameter[okLeft].ToExtended; 309 | except 310 | on E: Exception do 311 | raise EOpLeft.Create(E.Message); 312 | end; 313 | end 314 | else if LUseArea then 315 | raise EOpLeft.Create('The "-left" option is missing.'); 316 | 317 | // -top 318 | if okTop in glOptions then 319 | begin 320 | try 321 | LTop := OptionParameter[okTop].ToExtended; 322 | except 323 | on E: Exception do 324 | raise EOpTop.Create(E.Message); 325 | end; 326 | end 327 | else if LUseArea then 328 | raise EOpTop.Create('The "-top" option is missing.'); 329 | 330 | // -right 331 | if okRight in glOptions then 332 | begin 333 | try 334 | LRight := OptionParameter[okRight].ToExtended; 335 | except 336 | on E: Exception do 337 | raise EOpRight.Create(E.Message); 338 | end; 339 | end 340 | else if LUseArea then 341 | raise EOpRight.Create('The "-right" option is missing.'); 342 | 343 | // -bottom 344 | if okBottom in glOptions then 345 | begin 346 | try 347 | LBottom := OptionParameter[okBottom].ToExtended; 348 | except 349 | on E: Exception do 350 | raise EOpBottom.Create(E.Message); 351 | end; 352 | end 353 | else if LUseArea then 354 | raise EOpBottom.Create('The "-bottom" option is missing.'); 355 | 356 | // -areas 357 | if okAreas in glOptions then 358 | begin 359 | if LUseArea then 360 | raise EOpAreas.Create('Conflicting options are used to define the area.'); 361 | { #todo : Rename LUseRects to LUseAreas } 362 | LUseRects := True; 363 | SetLength(LAreaArray, 0); 364 | if not ImportAreas(LAreaArray, OptionParameter[okAreas]) then 365 | raise EOpAreas.Create('Error when processing rects.'); 366 | end; 367 | 368 | // -bbox 369 | if okBoundingBox in glOptions then 370 | begin 371 | if LUseArea or LUseRects then 372 | raise EOpBoundingBox.Create('Conflicting options are used to define the area.'); 373 | try 374 | ParseBoundingBox(OptionParameter[okBoundingBox], LBottom, LTop, LLeft, LRight); 375 | except 376 | on E: Exception do 377 | raise EOpBoundingBox.Create(E.Message); 378 | end; 379 | end; 380 | 381 | // -show-file-type 382 | if okShowFileType in glOptions then 383 | TildyEngine.ShowFileType := True; 384 | 385 | // skip-missing 386 | if okSkipMissing in glOptions then 387 | TildyEngine.SkipMissing := True; 388 | 389 | // skip-existing 390 | if okSkipExisting in glOptions then 391 | TildyEngine.SkipExisting := True; 392 | 393 | // -tile-res 394 | if okTileRes in glOptions then 395 | try 396 | TildyEngine.TileRes := OptionParameter[okTileRes].ToInteger; 397 | except 398 | on E: Exception do 399 | raise EOpTileRes.Create(E.Message); 400 | end; 401 | 402 | // -monochromes 403 | if okMonochromes in glOptions then 404 | begin 405 | LUseMonochromes := True; 406 | if not ImportMonochromes(TildyEngine, OptionParameter[okMonochromes]) then 407 | raise EOpMonochromes.Create('Error when processing monochromes.'); 408 | end; 409 | 410 | // -monochrome 411 | if okMonochrome in glOptions then 412 | begin 413 | if LUseMonochromes then 414 | raise EOpMonochrome.Create('Conflicting options (-monochromes) are used to define the area.'); 415 | try 416 | TildyEngine.Monochromes.Add(OptionParameter[okMonochrome]); 417 | except 418 | on E: Exception do 419 | raise EOpMonochrome.Create(E.Message); 420 | end; 421 | end; 422 | 423 | TildyEngine.OnProgress := @ProgressCLI; 424 | if LUseArea then 425 | TildyEngine.Download(LMinZoom, LMaxZoom, LBottom, LTop, LLeft, LRight) 426 | else if LUseRects then 427 | begin 428 | LAreasCount := Length(LAreaArray); 429 | for ia := 0 to LAreasCount - 1 do 430 | begin 431 | WriteLn('[Area ' + (ia + 1).ToString + '/' + LAreasCount.ToString + ']'); 432 | TildyEngine.Download(LMinZoom, LMaxZoom, LAreaArray[ia].Bottom, LAreaArray[ia].Top, LAreaArray[ia].Left, LAreaArray[ia].Right); 433 | WriteLn(); 434 | end 435 | end 436 | else 437 | TildyEngine.Download(LMinZoom, LMaxZoom); 438 | except 439 | on E: Exception do 440 | WriteLn(E.ClassName + ': ' + E.Message); 441 | end; 442 | 443 | TildyEngine.Free; 444 | Terminate; 445 | end; 446 | 447 | constructor ATildy.Create(TheOwner: TComponent); 448 | begin 449 | inherited Create(TheOwner); 450 | StopOnException := True; 451 | 452 | FFilters := TFilters.Create ; SetupFilters; 453 | FProviders := TProviders.Create; SetupProviders; 454 | 455 | FormatSettings.DecimalSeparator := '.'; 456 | end; 457 | 458 | destructor ATildy.Destroy; 459 | begin 460 | inherited Destroy; 461 | 462 | FreeAndNil(FFilters); 463 | FreeAndNil(FProviders); 464 | end; 465 | 466 | procedure ATildy.WriteHelp; 467 | begin 468 | WriteLn('tildy : Usage : '); 469 | WriteLn(' ./tildy [OPTION] [PARAMETER]...'); 470 | WriteLn(''); 471 | WriteLn('Donwload tiles from map providers.'); 472 | WriteLn(''); 473 | WriteLn(' Option Value Description'); 474 | WriteLn(' -provider, -p [String] use prepared provider. prepared providers:'); 475 | WriteLn(' - osm-standard - OpenStreetMap Standard'); 476 | WriteLn(' - railway-standard - OpenRailwayMap Standard'); 477 | WriteLn(' - railway-maxspeed - OpenRailwayMap Maxspeed'); 478 | WriteLn(' - railway-electrification - OpenRailwayMap Electrification'); 479 | WriteLn(' -providers,-ps [String] import providers from ini file. read usage instructions.'); 480 | WriteLn(' -layers, -ls [String] use layers ini file. read usage instructions.'); 481 | WriteLn(' -areas, -as [String] use areas ini file. read usage instructions.'); 482 | WriteLn(' -monochrome, -m [String] color of monochrome tiles that should not be saved.'); 483 | WriteLn(' -monochromes, -ms [String] use monochromes ini file. read usage instructions.'); 484 | WriteLn(' -out, -o [String] out path, default is current path in dir "tiles",'); 485 | WriteLn(' support a pattern-generated path. keywords:'); 486 | WriteLn(' - {p} - provider name'); 487 | WriteLn(' - {x} - x number of tile'); 488 | WriteLn(' - {y} - y number of tile'); 489 | WriteLn(' - {z} - zoom number'); 490 | WriteLn(' -cache, -c [String] path to the folder with the provider''s tiles that have already been downloaded. keywords:'); 491 | WriteLn(' - {p} - provider name'); 492 | WriteLn(' - {x} - x number of tile'); 493 | WriteLn(' - {y} - y number of tile'); 494 | WriteLn(' - {z} - zoom number'); 495 | WriteLn(' -use-cache-only, -uco [x] utility will only work with cached tiles.'); 496 | WriteLn(' -min-zoom, -z [Unsigned Integer] lower zoom limit. the range is specific to each provider.'); 497 | WriteLn(' -max-zoom, -Z [Unsigned Integer] highest zoom limit. the range is specific to each provider.'); 498 | WriteLn(' -left, -l [Double] left border of the downloaded area (longitude).'); 499 | WriteLn(' -top, -t [Double] top border of the downloaded area (latitude).'); 500 | WriteLn(' -right, -r [Double] right border of the downloaded area (longitude).'); 501 | WriteLn(' -bottom, -b [Double] bottom border of the downloaded area (latitude).'); 502 | WriteLn(' -bbox, -b [Double,Double,Double,Double] MinLon,MinLat,MaxLon,MaxLat.'); 503 | WriteLn(' -show-file-type, -sft [x] show file extension in filename.'); 504 | WriteLn(' -skip-existing, -ske [x] skipping tiles that already exist on the disk when received from the server'); 505 | WriteLn(' -skip-missing, -skm [x] skim missing tiles from provider.'); 506 | WriteLn(' -filter, -f [String] applying a filter. available filters:'); 507 | WriteLn(' - grayscale'); 508 | WriteLn(' -tile-res, -res [Unsigned Integer] resolution of the saved images.'); 509 | WriteLn(' -version, -v [x] show the version.'); 510 | WriteLn(' -help, -h [x] show current help.'); 511 | WriteLn(''); 512 | WriteLn('Examples:'); 513 | WriteLn(' ./tildy -p osm-standard -z 1 -Z 7'); 514 | WriteLn(' ./tildy -p osm-standard -z 1 -Z 7 -o tiles/{p}/{z}/{x}_{y}'); 515 | end; 516 | 517 | function ATildy.ImportProviders(AFilePath: String): Boolean; 518 | const 519 | _ProviderSectionStr = 'Provider'; 520 | var 521 | LIniFile: TMemIniFile = nil; 522 | LSection: TStringList = nil; 523 | LIdent, LName, LURL, LUseCacheOnly: String; 524 | LMemoryStream: TMemoryStream = nil; 525 | LLastProvider: Integer; 526 | begin 527 | Result := True; 528 | try 529 | LMemoryStream := TMemoryStream.Create; 530 | LMemoryStream.LoadFromFile(AFilePath); 531 | LIniFile := TMemIniFile.Create(LMemoryStream); 532 | LSection := TStringList.Create; 533 | 534 | LIniFile.ReadSection(_ProviderSectionStr, LSection); 535 | while LSection.Count > 0 do 536 | begin 537 | LIdent := LIniFile.ReadString(_ProviderSectionStr, 'ident', ''); 538 | LName := LIniFile.ReadString(_ProviderSectionStr, 'name', ''); 539 | LURL := LIniFile.ReadString(_ProviderSectionStr, 'url', ''); 540 | LLastProvider := Providers.Add(LIdent, LName, LURL, TEPSG3857.Create); 541 | if LIniFile.ValueExists(_ProviderSectionStr, 'cache') then 542 | Providers.Data[LLastProvider].CachePath := LIniFile.ReadString(_ProviderSectionStr, 'cache', ''); 543 | if LIniFile.ValueExists(_ProviderSectionStr, 'use-cache-only') then 544 | begin 545 | LUseCacheOnly := LIniFile.ReadString(_ProviderSectionStr, 'use-cache-only', 'yes'); 546 | if LUseCacheOnly = 'yes' then 547 | Providers.Data[LLastProvider].UseCacheOnly := True 548 | end; 549 | 550 | LIniFile.EraseSection(_ProviderSectionStr); 551 | LIniFile.ReadSection(_ProviderSectionStr, LSection); 552 | end; 553 | 554 | LSection.Free; 555 | LIniFile.Free; 556 | LMemoryStream.Free; 557 | except 558 | on E: Exception do 559 | begin 560 | Result := False; 561 | if Assigned(LSection) then FreeAndNil(LSection); 562 | if Assigned(LIniFile) then FreeAndNil(LIniFile); 563 | if Assigned(LMemoryStream) then FreeAndNil(LMemoryStream); 564 | WriteLn(E.ClassName + ': ' + E.Message); 565 | end; 566 | end; 567 | end; 568 | 569 | function ATildy.ImportLayers(ATildyEngine: TTildyEngine; AFilePath: String): Boolean; 570 | const 571 | _LayerSectionStr = 'Layer'; 572 | var 573 | LIniFile: TMemIniFile = nil; 574 | LSection: TStringList = nil; 575 | LCreatedLayerIndex: Integer; 576 | LByte: Byte; 577 | LString: String; 578 | LMemoryStream: TMemoryStream = nil; 579 | begin 580 | Result := True; 581 | try 582 | LMemoryStream := TMemoryStream.Create; 583 | LMemoryStream.LoadFromFile(AFilePath); 584 | LIniFile := TMemIniFile.Create(LMemoryStream); 585 | LSection := TStringList.Create; 586 | 587 | LIniFile.ReadSection(_LayerSectionStr, LSection); 588 | while LSection.Count > 0 do 589 | begin 590 | LString := LIniFile.ReadString(_LayerSectionStr, 'Provider', ''); 591 | LCreatedLayerIndex := ATildyEngine.Layers.Add(Providers[LString]); 592 | LByte := LIniFile.ReadInteger(_LayerSectionStr, 'Opacity', 255); 593 | ATildyEngine.Layers[LCreatedLayerIndex].Opacity := LByte; 594 | LString := LIniFile.ReadString(_LayerSectionStr, 'Filter', ''); 595 | if not LString.IsEmpty then 596 | ATildyEngine.Layers[LCreatedLayerIndex].Filter := Filters[LString]; 597 | 598 | LIniFile.EraseSection(_LayerSectionStr); 599 | LIniFile.ReadSection(_LayerSectionStr, LSection); 600 | end; 601 | 602 | LSection.Free; 603 | LIniFile.Free; 604 | LMemoryStream.Free; 605 | except 606 | on E: Exception do 607 | begin 608 | Result := False; 609 | if Assigned(LSection) then FreeAndNil(LSection); 610 | if Assigned(LIniFile) then FreeAndNil(LIniFile); 611 | if Assigned(LMemoryStream) then FreeAndNil(LMemoryStream); 612 | WriteLn(E.ClassName + ': ' + E.Message); 613 | end; 614 | end; 615 | end; 616 | 617 | function ATildy.ImportMonochromes(ATildyEngine: TTildyEngine; 618 | AFilePath: String): Boolean; 619 | const 620 | _MonochromeSectionStr = 'Monochrome'; 621 | var 622 | LIniFile: TMemIniFile = nil; 623 | LSection: TStringList = nil; 624 | LString: String; 625 | LMemoryStream: TMemoryStream = nil; 626 | begin 627 | Result := True; 628 | try 629 | LMemoryStream := TMemoryStream.Create; 630 | LMemoryStream.LoadFromFile(AFilePath); 631 | LIniFile := TMemIniFile.Create(LMemoryStream); 632 | LSection := TStringList.Create; 633 | 634 | LIniFile.ReadSection(_MonochromeSectionStr, LSection); 635 | while LSection.Count > 0 do 636 | begin 637 | LString := LIniFile.ReadString(_MonochromeSectionStr, 'Color', ''); 638 | ATildyEngine.Monochromes.Add(LString); 639 | 640 | LIniFile.EraseSection(_MonochromeSectionStr); 641 | LIniFile.ReadSection(_MonochromeSectionStr, LSection); 642 | end; 643 | 644 | LSection.Free; 645 | LIniFile.Free; 646 | LMemoryStream.Free; 647 | except 648 | on E: Exception do 649 | begin 650 | Result := False; 651 | if Assigned(LSection) then FreeAndNil(LSection); 652 | if Assigned(LIniFile) then FreeAndNil(LIniFile); 653 | if Assigned(LMemoryStream) then FreeAndNil(LMemoryStream); 654 | WriteLn(E.ClassName + ': ' + E.Message); 655 | end; 656 | end; 657 | end; 658 | 659 | function ATildy.ImportAreas(var AAreaArray: TAreaArray; AFilePath: String): Boolean; 660 | const 661 | _SectionStr = 'Area'; 662 | var 663 | LIniFile: TMemIniFile = nil; 664 | LSection: TStringList = nil; 665 | LMemoryStream: TMemoryStream = nil; 666 | LCount: Integer = 0; 667 | LAllValuesExists: Boolean = False; 668 | begin 669 | Result := True; 670 | try 671 | LMemoryStream := TMemoryStream.Create; 672 | LMemoryStream.LoadFromFile(AFilePath); 673 | LIniFile := TMemIniFile.Create(LMemoryStream); 674 | LSection := TStringList.Create; 675 | 676 | LIniFile.ReadSection(_SectionStr, LSection); 677 | while LSection.Count > 0 do 678 | begin 679 | Inc(LCount); 680 | SetLength(AAreaArray, LCount); 681 | LAllValuesExists := (LIniFile.ValueExists(_SectionStr, 'left') 682 | and LIniFile.ValueExists(_SectionStr, 'top') 683 | and LIniFile.ValueExists(_SectionStr, 'right') 684 | and LIniFile.ValueExists(_SectionStr, 'bottom')); 685 | if not LAllValuesExists then 686 | raise Exception.Create(_SectionStr + ' number ' + LCount.ToString + ' has not all values'); 687 | 688 | AAreaArray[LCount-1].Left := LIniFile.ReadFloat(_SectionStr, 'left', 0.0); 689 | AAreaArray[LCount-1].Top := LIniFile.ReadFloat(_SectionStr, 'top', 0.0); 690 | AAreaArray[LCount-1].Right := LIniFile.ReadFloat(_SectionStr, 'right', 0.0); 691 | AAreaArray[LCount-1].Bottom := LIniFile.ReadFloat(_SectionStr, 'bottom', 0.0); 692 | 693 | LIniFile.EraseSection(_SectionStr); 694 | LIniFile.ReadSection(_SectionStr, LSection); 695 | end; 696 | 697 | LSection.Free; 698 | LIniFile.Free; 699 | LMemoryStream.Free; 700 | except 701 | on E: Exception do 702 | begin 703 | Result := False; 704 | if Assigned(LSection) then FreeAndNil(LSection); 705 | if Assigned(LIniFile) then FreeAndNil(LIniFile); 706 | if Assigned(LMemoryStream) then FreeAndNil(LMemoryStream); 707 | WriteLn(E.ClassName + ': ' + E.Message); 708 | end; 709 | end; 710 | end; 711 | 712 | 713 | 714 | var 715 | apptildy: ATildy; 716 | 717 | {$R *.res} 718 | 719 | begin 720 | apptildy := ATildy.Create(nil); 721 | apptildy.Title := 'tildy'; 722 | apptildy.Run; 723 | apptildy.Free; 724 | end. 725 | 726 | -------------------------------------------------------------------------------- /src/gui/forms/tildy.gui.forms.main.pas: -------------------------------------------------------------------------------- 1 | unit Tildy.GUI.Forms.Main; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, 9 | ComCtrls, ActnList, EditBtn, CheckLst, Buttons, Menus,nullable, 10 | LazFileUtils, IniFiles, Types, System.UITypes, 11 | // Translate 12 | LCLTranslator, Tildy.GUI.i18n.Runtime, Tildy.GUI.i18n.StrConsts, 13 | // MapView 14 | mvMapViewer, mvDLECache, mvEngine, mvTypes, mvDE_BGRA, 15 | mvDLEFpc, mvPluginCommon, mvAreaSelectionPlugin, mvPlugins, 16 | mvMapProvider, 17 | // Brand 18 | Tildy.GUI.Brand.Colors, 19 | // Dialogs 20 | Tildy.GUI.Dialogs.AddLayers, Tildy.GUI.Dialogs.EditAreaName; 21 | 22 | type 23 | 24 | TNullableInt = specialize TNullable<Integer>; 25 | 26 | { TfMain } 27 | 28 | TfMain = class(TForm, ILocalizableForm) 29 | actAreasAdd: TAction; 30 | actAreasDelete: TAction; 31 | actAreasEditName: TAction; 32 | actAreasImport: TAction; 33 | actAreasExport: TAction; 34 | actAreasUp: TAction; 35 | actAreasDown: TAction; 36 | actTileInfo: TAction; 37 | actZoomIn: TAction; 38 | actZoomOut: TAction; 39 | actSelectAll: TAction; 40 | ActionsCommon: TActionList; 41 | actLayersAdd: TAction; 42 | actLayersDelete: TAction; 43 | actLayersDown: TAction; 44 | actLayersUp: TAction; 45 | ActionsAreas: TActionList; 46 | ActionsLayers: TActionList; 47 | btnAddArea: TSpeedButton; 48 | btnAreasDown: TSpeedButton; 49 | btnLayersDelete: TSpeedButton; 50 | btnDeleteArea: TSpeedButton; 51 | btnAreasImport: TSpeedButton; 52 | btnLayersDown: TSpeedButton; 53 | btnZoomOut: TSpeedButton; 54 | chkCache: TCheckBox; 55 | ImagesPrimary: TImageList; 56 | ImagesDark: TImageList; 57 | lblAreas: TLabel; 58 | lblProviderMap: TLabel; 59 | lblCache: TLabel; 60 | lblLayers: TLabel; 61 | LayersList: TCheckListBox; 62 | AreasList: TListBox; 63 | MainMenu: TMainMenu; 64 | MapView: TMapView; 65 | miLangaugeEn: TMenuItem; 66 | miLangaugeRu: TMenuItem; 67 | miLanguage: TMenuItem; 68 | MvBGRADrawingEngine: TMvBGRADrawingEngine; 69 | MVDEFPC: TMVDEFPC; 70 | MvPluginManager: TMvPluginManager; 71 | btnAreasUp: TSpeedButton; 72 | btnLayersUp: TSpeedButton; 73 | btnZoomIn: TSpeedButton; 74 | btnTileInfo: TSpeedButton; 75 | TileInfoPlugin: TTileInfoPlugin; 76 | OpenDialog: TOpenDialog; 77 | panAreas: TPanel; 78 | panOffsetBottom: TPanel; 79 | panProviderMap: TPanel; 80 | panCache: TPanel; 81 | panLayers: TPanel; 82 | lblDebugZoom: TLabel; 83 | lblDebugLon: TLabel; 84 | lblDebugLat: TLabel; 85 | panDebug: TPanel; 86 | PopupMapView: TPopupMenu; 87 | btnLayersAdd: TSpeedButton; 88 | btnAreasExport: TSpeedButton; 89 | SaveDialog: TSaveDialog; 90 | btnAreasEdit: TSpeedButton; 91 | DirectoryCache: TDirectoryEdit; 92 | MVDECache: TMVDECache; 93 | panMV: TPanel; 94 | ProviderVariationsMap: TComboBox; 95 | mvScrollBox: TScrollBox; 96 | mvSsettings: TSplitter; 97 | procedure actAreasAddExecute(Sender: TObject); 98 | procedure actAreasDeleteExecute(Sender: TObject); 99 | procedure actAreasDownExecute(Sender: TObject); 100 | procedure actAreasEditNameExecute(Sender: TObject); 101 | procedure actAreasExportExecute(Sender: TObject); 102 | procedure actAreasImportExecute(Sender: TObject); 103 | procedure actAreasUpExecute(Sender: TObject); 104 | procedure actEditAreaNameExecute(Sender: TObject); 105 | procedure ActionsAreasUpdate(AAction: TBasicAction; var Handled: Boolean); 106 | procedure ActionsCommonUpdate(AAction: TBasicAction; var Handled: Boolean); 107 | procedure ActionsLayersUpdate(AAction: TBasicAction; var Handled: Boolean); 108 | procedure actLayersAddExecute(Sender: TObject); 109 | procedure actLayersDeleteExecute(Sender: TObject); 110 | procedure actLayersDownExecute(Sender: TObject); 111 | procedure actLayersUpExecute(Sender: TObject); 112 | procedure actSelectAllExecute(Sender: TObject); 113 | procedure actTileInfoExecute(Sender: TObject); 114 | procedure actZoomInExecute(Sender: TObject); 115 | procedure actZoomOutExecute(Sender: TObject); 116 | procedure AreasListDblClick(Sender: TObject); 117 | procedure AreasListSelectionChange(Sender: TObject; User: boolean); 118 | procedure AreasUpDownClick(Sender: TObject; Button: TUDBtnType); 119 | procedure chkCacheChange(Sender: TObject); 120 | procedure DirectoryCacheChange(Sender: TObject); 121 | procedure FormActivate(Sender: TObject); 122 | procedure FormCreate(Sender: TObject); 123 | procedure LayersListClickCheck(Sender: TObject); 124 | procedure LayersListDragDrop(Sender, Source: TObject; X, Y: Integer); 125 | procedure LayersListDragOver(Sender, Source: TObject; X, Y: Integer; 126 | State: TDragState; var Accept: Boolean); 127 | procedure MapViewCenterMoving(Sender: TObject; var NewCenter: TRealPoint; 128 | var Allow: Boolean); 129 | procedure MapViewMouseMove(Sender: TObject; Shift: TShiftState; X, 130 | Y: Integer); 131 | procedure MapViewResize(Sender: TObject); 132 | procedure MapViewZoomChange(Sender: TObject); 133 | procedure miLangaugeEnClick(Sender: TObject); 134 | procedure miLangaugeRuClick(Sender: TObject); 135 | procedure ProviderVariationsMapChange(Sender: TObject); 136 | procedure LayersUpDownClick(Sender: TObject; Button: TUDBtnType); 137 | procedure ProviderVariationsMapSelect(Sender: TObject); 138 | procedure OnSelectedAreaBeginChange(Sender: TObject); 139 | strict private 140 | FCurrentImageList: TImageList; 141 | FPluginCount: Integer; 142 | FPrevAreaIndex: TNullableInt; 143 | FShowTileInfo: Boolean; 144 | procedure SetShowTileInfo(AValue: Boolean); 145 | procedure ReloadLayersList; 146 | procedure DetectAndApplyTheme; 147 | procedure UpdateDebugZoom; 148 | procedure UpdateDebugCursorCoordinate(const X, Y: Integer); 149 | procedure PrepareAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 150 | property ShowTileInfo: Boolean read FShowTileInfo write SetShowTileInfo; 151 | public 152 | procedure TranslationChanged; 153 | property CurrentImages: TImageList read FCurrentImageList; 154 | end; 155 | 156 | var 157 | fMain: TfMain; 158 | 159 | implementation 160 | 161 | {$R *.lfm} 162 | 163 | const 164 | ImgIndTileInfo: array[Boolean] of Integer = (12, 13); 165 | 166 | { TfMain } 167 | 168 | procedure TfMain.AreasListDblClick(Sender: TObject); 169 | begin 170 | actAreasEditName.Execute; 171 | end; 172 | 173 | procedure TfMain.ActionsAreasUpdate(AAction: TBasicAction; var Handled: Boolean); 174 | var 175 | IsItemSelected: Boolean; 176 | begin 177 | IsItemSelected := (AreasList.ItemIndex <> -1) and AreasList.Selected[AreasList.ItemIndex]; 178 | 179 | actAreasDelete.Enabled := IsItemSelected; 180 | actAreasEditName.Enabled := IsItemSelected; 181 | actAreasUp.Enabled := IsItemSelected and (AreasList.ItemIndex > 0); 182 | actAreasDown.Enabled := IsItemSelected and (AreasList.ItemIndex < (AreasList.Count - 1)); 183 | actAreasExport.Enabled := AreasList.Count > 0; 184 | 185 | Handled := True; 186 | end; 187 | 188 | procedure TfMain.ActionsCommonUpdate(AAction: TBasicAction; var Handled: Boolean 189 | ); 190 | begin 191 | actZoomIn.Enabled := MapView.Zoom <> MapView.ZoomMax; 192 | actZoomOut.Enabled := MapView.Zoom <> MapView.ZoomMin; 193 | 194 | Handled := True; 195 | end; 196 | 197 | procedure TfMain.ActionsLayersUpdate(AAction: TBasicAction; var Handled: Boolean 198 | ); 199 | var 200 | IsItemSelected: Boolean; 201 | begin 202 | IsItemSelected := (LayersList.ItemIndex <> -1) and LayersList.Selected[LayersList.ItemIndex]; 203 | 204 | actLayersDelete.Enabled := IsItemSelected; 205 | actLayersUp.Enabled := IsItemSelected and (LayersList.ItemIndex > 0); 206 | actLayersDown.Enabled := IsItemSelected and (LayersList.ItemIndex < (LayersList.Count - 1)); 207 | 208 | Handled := True; 209 | end; 210 | 211 | procedure TfMain.actLayersAddExecute(Sender: TObject); 212 | begin 213 | fAddLayers := TfAddLayers.Create(MapView); 214 | if fAddLayers.ShowModal = mrOK then 215 | begin 216 | ReloadLayersList; 217 | end; 218 | FreeAndNil(fAddLayers); 219 | end; 220 | 221 | procedure TfMain.actLayersDeleteExecute(Sender: TObject); 222 | begin 223 | MapView.Layers.Delete(LayersList.ItemIndex); 224 | LayersList.Items.Delete(LayersList.ItemIndex); 225 | end; 226 | 227 | procedure TfMain.actLayersDownExecute(Sender: TObject); 228 | var 229 | NewIndex: Integer; 230 | begin 231 | if (LayersList.ItemIndex = -1) or (LayersList.ItemIndex = LayersList.Count-1) then Exit; 232 | 233 | NewIndex := LayersList.ItemIndex + 1; 234 | MapView.Layers.Move(LayersList.ItemIndex, NewIndex); 235 | LayersList.Items.Move(LayersList.ItemIndex, NewIndex); 236 | LayersList.Selected[NewIndex] := True; 237 | end; 238 | 239 | procedure TfMain.actLayersUpExecute(Sender: TObject); 240 | var 241 | NewIndex: Integer; 242 | begin 243 | if LayersList.ItemIndex < 1 then Exit; 244 | 245 | NewIndex := LayersList.ItemIndex - 1; 246 | MapView.Layers.Move(LayersList.ItemIndex, NewIndex); 247 | LayersList.Items.Move(LayersList.ItemIndex, NewIndex); 248 | LayersList.Selected[NewIndex] := True; 249 | end; 250 | 251 | procedure TfMain.actSelectAllExecute(Sender: TObject); 252 | begin 253 | if not AreasList.Focused then Exit; 254 | 255 | AreasList.SelectAll; 256 | end; 257 | 258 | procedure TfMain.actTileInfoExecute(Sender: TObject); 259 | begin 260 | ShowTileInfo := not ShowTileInfo; 261 | end; 262 | 263 | procedure TfMain.actZoomInExecute(Sender: TObject); 264 | begin 265 | MapView.Zoom := MapView.Zoom + 1; 266 | end; 267 | 268 | procedure TfMain.actZoomOutExecute(Sender: TObject); 269 | begin 270 | MapView.Zoom := MapView.Zoom - 1; 271 | end; 272 | 273 | procedure TfMain.actAreasImportExecute(Sender: TObject); 274 | const 275 | _SectionStr = 'Area'; 276 | var 277 | LIniFile: TMemIniFile = nil; 278 | LSection: TStringList = nil; 279 | LMemoryStream: TMemoryStream = nil; 280 | LCount: Integer = 0; 281 | LAllValuesExists: Boolean = False; 282 | LStringList: TStringList = nil; 283 | LRealAreaArray: array of TRealArea; 284 | LNameArray: array of String; 285 | LAreaSelectionPlugin: TAreaSelectionPlugin; 286 | i: Integer; 287 | begin 288 | if not OpenDialog.Execute then Exit; 289 | 290 | try 291 | LStringList := TStringList.Create; 292 | LMemoryStream := TMemoryStream.Create; 293 | LSection := TStringList.Create; 294 | try 295 | LMemoryStream.LoadFromFile(OpenDialog.FileName); 296 | LIniFile := TMemIniFile.Create(LMemoryStream); 297 | SetLength(LRealAreaArray, LCount); 298 | 299 | LIniFile.ReadSection(_SectionStr, LSection); 300 | while LSection.Count > 0 do 301 | begin 302 | Inc(LCount); 303 | SetLength(LRealAreaArray, LCount); 304 | SetLength(LNameArray , LCount); 305 | LAllValuesExists := LIniFile.ValueExists(_SectionStr, 'left') 306 | and LIniFile.ValueExists(_SectionStr, 'top') 307 | and LIniFile.ValueExists(_SectionStr, 'right') 308 | and LIniFile.ValueExists(_SectionStr, 'bottom'); 309 | if not LAllValuesExists then 310 | raise Exception.Create(_SectionStr + ' number ' + LCount.ToString + ' has not all values'); 311 | 312 | if LIniFile.ValueExists(_SectionStr, 'name') then 313 | LStringList.Add(LIniFile.ReadString(_SectionStr, 'name', '')) 314 | else 315 | LStringList.Add(String.Empty); 316 | LRealAreaArray[LCount-1].TopLeft.Lon := LIniFile.ReadFloat(_SectionStr, 'left', 0.0); 317 | LRealAreaArray[LCount-1].TopLeft.Lat := LIniFile.ReadFloat(_SectionStr, 'top', 0.0); 318 | LRealAreaArray[LCount-1].BottomRight.Lon := LIniFile.ReadFloat(_SectionStr, 'right', 0.0); 319 | LRealAreaArray[LCount-1].BottomRight.Lat := LIniFile.ReadFloat(_SectionStr, 'bottom', 0.0); 320 | 321 | LIniFile.EraseSection(_SectionStr); 322 | LIniFile.ReadSection(_SectionStr, LSection); 323 | end; 324 | 325 | for i := 0 to AreasList.Count-1 do 326 | begin 327 | LAreaSelectionPlugin := AreasList.Items.Objects[i] as TAreaSelectionPlugin; 328 | MvPluginManager.PluginList.Delete(MvPluginManager.PluginList.IndexOf(LAreaSelectionPlugin)); 329 | end; 330 | 331 | if LStringList.Count > 0 then 332 | begin 333 | FPrevAreaIndex.Clear; 334 | AreasList.Clear; 335 | for i := 0 to LStringList.Count-1 do 336 | begin 337 | LAreaSelectionPlugin := TAreaSelectionPlugin.Create(MvPluginManager); 338 | LAreaSelectionPlugin.MapView := MapView; 339 | LAreaSelectionPlugin.OnSelectedAreaBeginChange := @OnSelectedAreaBeginChange; 340 | LAreaSelectionPlugin.SelectedArea.Area := LRealAreaArray[i]; 341 | if LStringList[i].IsEmpty then 342 | LAreaSelectionPlugin.Caption := 'Area' + i.ToString 343 | else 344 | LAreaSelectionPlugin.Caption := LStringList[i]; 345 | AreasList.AddItem(LAreaSelectionPlugin.Caption, LAreaSelectionPlugin); 346 | end; 347 | end; 348 | except 349 | on E: Exception do 350 | begin 351 | WriteLn(E.ClassName + ': ' + E.Message); 352 | end; 353 | end; 354 | finally 355 | LStringList.Free; 356 | LSection.Free; 357 | LIniFile.Free; 358 | LMemoryStream.Free; 359 | end; 360 | end; 361 | 362 | procedure TfMain.actAreasUpExecute(Sender: TObject); 363 | 364 | procedure _MovePrevAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 365 | var 366 | LCurPluginPos: Integer; 367 | i: Integer; 368 | PrevAreaSelectionPlugin: TAreaSelectionPlugin = nil; 369 | begin 370 | LCurPluginPos := MvPluginManager.PluginList.IndexOf(AAreaSelectionPlugin); 371 | for i := LCurPluginPos downto 0 do 372 | if MvPluginManager.PluginList[i] is TAreaSelectionPlugin then 373 | begin 374 | PrevAreaSelectionPlugin := MvPluginManager.PluginList[i] as TAreaSelectionPlugin; 375 | break; 376 | end; 377 | if not Assigned(PrevAreaSelectionPlugin) then Exit; 378 | MvPluginManager.PluginList.Move(LCurPluginPos, i); 379 | end; 380 | 381 | var 382 | LNewStringPos: Integer; 383 | LAreaSelectionPlugin: TAreaSelectionPlugin; 384 | begin 385 | if AreasList.ItemIndex < 1 then Exit; 386 | 387 | { Move plugin first } 388 | LAreaSelectionPlugin := AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin; 389 | _MovePrevAreaSelectionPlugin(LAreaSelectionPlugin); 390 | { Move item in AreasList then } 391 | LNewStringPos := AreasList.ItemIndex - 1; 392 | AreasList.Items.Move(AreasList.ItemIndex, LNewStringPos); 393 | AreasList.ItemIndex := LNewStringPos; 394 | AreasList.Selected[LNewStringPos] := True; 395 | end; 396 | 397 | procedure TfMain.actEditAreaNameExecute(Sender: TObject); 398 | begin 399 | fEditAreaName.AreaName := AreasList.Items[AreasList.ItemIndex]; 400 | fEditAreaName.ShowModal; 401 | if fEditAreaName.ModalResult = mrOK then 402 | begin 403 | AreasList.Items[AreasList.ItemIndex] := fEditAreaName.AreaName; 404 | try 405 | (AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin).Caption := fEditAreaName.AreaName; 406 | finally end; 407 | end; 408 | end; 409 | 410 | procedure TfMain.actAreasExportExecute(Sender: TObject); 411 | const 412 | _SectionStr = '[Area]'; 413 | var 414 | LStringList: TStringList = nil; 415 | LAreaSelectionPlugin: TAreaSelectionPlugin; 416 | i: Integer; 417 | begin 418 | if not SaveDialog.Execute then Exit; 419 | 420 | try 421 | LStringList := TStringList.Create; 422 | for i := 0 to AreasList.Count-1 do 423 | begin 424 | LStringList.Add(_SectionStr); 425 | LAreaSelectionPlugin := AreasList.Items.Objects[i] as TAreaSelectionPlugin; 426 | LStringList.Add('name=' + LAreaSelectionPlugin.Caption); 427 | LStringList.Add('left=' + LAreaSelectionPlugin.SelectedArea.West.ToString); 428 | LStringList.Add('top=' + LAreaSelectionPlugin.SelectedArea.North.ToString); 429 | LStringList.Add('right=' + LAreaSelectionPlugin.SelectedArea.East.ToString); 430 | LStringList.Add('bottom=' + LAreaSelectionPlugin.SelectedArea.South.ToString); 431 | LStringList.Add(''); 432 | end; 433 | LStringList.SaveToFile(SaveDialog.FileName); 434 | finally 435 | if Assigned(LStringList) then FreeAndNil(LStringList); 436 | end; 437 | end; 438 | 439 | procedure TfMain.actAreasAddExecute(Sender: TObject); 440 | var 441 | LAreaSelectPlugin: TAreaSelectionPlugin; 442 | begin 443 | LAreaSelectPlugin := TAreaSelectionPlugin.Create(MvPluginManager); 444 | LAreaSelectPlugin.MapView := MapView; 445 | LAreaSelectPlugin.OnSelectedAreaBeginChange := @OnSelectedAreaBeginChange; 446 | Inc(FPluginCount); 447 | LAreaSelectPlugin.Caption := 'Area' + FPluginCount.ToString; 448 | AreasList.AddItem(LAreaSelectPlugin.Caption, LAreaSelectPlugin); 449 | PrepareAreaSelectionPlugin(LAreaSelectPlugin); 450 | end; 451 | 452 | procedure TfMain.actAreasDeleteExecute(Sender: TObject); 453 | var 454 | LMvCustomPlugin: TMvCustomPlugin; 455 | i: Integer; 456 | begin 457 | if AreasList.ItemIndex = FPrevAreaIndex.Value then 458 | FPrevAreaIndex.Clear; 459 | 460 | for i := AreasList.Count - 1 downto 0 do 461 | begin 462 | if not AreasList.Selected[i] then Continue; 463 | 464 | LMvCustomPlugin := AreasList.Items.Objects[i] as TMvCustomPlugin; 465 | MvPluginManager.PluginList.Delete(MvPluginManager.PluginList.IndexOf(LMvCustomPlugin)); 466 | AreasList.Items.Delete(i); 467 | end; 468 | 469 | MapView.Refresh; 470 | end; 471 | 472 | procedure TfMain.actAreasDownExecute(Sender: TObject); 473 | 474 | procedure _MoveNextAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 475 | var 476 | LCurPluginPos: Integer; 477 | i: Integer; 478 | NextAreaSelectionPlugin: TAreaSelectionPlugin = nil; 479 | begin 480 | LCurPluginPos := MvPluginManager.PluginList.IndexOf(AAreaSelectionPlugin); 481 | for i := LCurPluginPos to MvPluginManager.PluginList.Count - 1 do 482 | if MvPluginManager.PluginList[i] is TAreaSelectionPlugin then 483 | begin 484 | NextAreaSelectionPlugin := MvPluginManager.PluginList[i] as TAreaSelectionPlugin; 485 | break; 486 | end; 487 | if not Assigned(NextAreaSelectionPlugin) then Exit; 488 | MvPluginManager.PluginList.Move(LCurPluginPos, i); 489 | end; 490 | 491 | var 492 | LNewStringPos: Integer; 493 | LAreaSelectionPlugin: TAreaSelectionPlugin; 494 | begin 495 | if AreasList.ItemIndex = -1 then Exit; 496 | 497 | if (AreasList.ItemIndex = (AreasList.Count - 1)) then Exit; 498 | { Move plugin first } 499 | LAreaSelectionPlugin := AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin; 500 | _MoveNextAreaSelectionPlugin(LAreaSelectionPlugin); 501 | { Move item in AreasList then } 502 | LNewStringPos := AreasList.ItemIndex + 1; 503 | AreasList.Items.Move(AreasList.ItemIndex, LNewStringPos); 504 | AreasList.ItemIndex := LNewStringPos; 505 | end; 506 | 507 | procedure TfMain.actAreasEditNameExecute(Sender: TObject); 508 | begin 509 | fEditAreaName := TfEditAreaName.Create(MapView); 510 | fEditAreaName.AreaName := AreasList.Items[AreasList.ItemIndex]; 511 | if fEditAreaName.ShowModal = mrOK then 512 | begin 513 | AreasList.Items[AreasList.ItemIndex] := fEditAreaName.AreaName; 514 | try 515 | (AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin).Caption := fEditAreaName.AreaName; 516 | finally end; 517 | end; 518 | FreeAndNil(fEditAreaName); 519 | end; 520 | 521 | procedure TfMain.AreasListSelectionChange(Sender: TObject; User: boolean); 522 | var 523 | LAreaSelectPlugin: TAreaSelectionPlugin; 524 | begin 525 | if AreasList.ItemIndex = -1 then Exit; 526 | 527 | if FPrevAreaIndex.HasValue then 528 | begin 529 | LAreaSelectPlugin := AreasList.Items.Objects[FPrevAreaIndex.Value] as TAreaSelectionPlugin; 530 | LAreaSelectPlugin.Pen.Color := BrandColors.Accent; 531 | end; 532 | 533 | LAreaSelectPlugin := AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin; 534 | LAreaSelectPlugin.Pen.Color := TColorRec.Orange; 535 | FPrevAreaIndex := AreasList.ItemIndex; 536 | end; 537 | 538 | procedure TfMain.AreasUpDownClick(Sender: TObject; Button: TUDBtnType); 539 | 540 | procedure _MoveNextAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 541 | var 542 | LCurPluginPos: Integer; 543 | i: Integer; 544 | NextAreaSelectionPlugin: TAreaSelectionPlugin = nil; 545 | begin 546 | LCurPluginPos := MvPluginManager.PluginList.IndexOf(AAreaSelectionPlugin); 547 | for i := LCurPluginPos to MvPluginManager.PluginList.Count - 1 do 548 | if MvPluginManager.PluginList[i] is TAreaSelectionPlugin then 549 | begin 550 | NextAreaSelectionPlugin := MvPluginManager.PluginList[i] as TAreaSelectionPlugin; 551 | break; 552 | end; 553 | if not Assigned(NextAreaSelectionPlugin) then Exit; 554 | MvPluginManager.PluginList.Move(LCurPluginPos, i); 555 | end; 556 | 557 | procedure _MovePrevAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 558 | var 559 | LCurPluginPos: Integer; 560 | i: Integer; 561 | PrevAreaSelectionPlugin: TAreaSelectionPlugin = nil; 562 | begin 563 | LCurPluginPos := MvPluginManager.PluginList.IndexOf(AAreaSelectionPlugin); 564 | for i := LCurPluginPos downto 0 do 565 | if MvPluginManager.PluginList[i] is TAreaSelectionPlugin then 566 | begin 567 | PrevAreaSelectionPlugin := MvPluginManager.PluginList[i] as TAreaSelectionPlugin; 568 | break; 569 | end; 570 | if not Assigned(PrevAreaSelectionPlugin) then Exit; 571 | MvPluginManager.PluginList.Move(LCurPluginPos, i); 572 | end; 573 | 574 | var 575 | LNewStringPos: Integer; 576 | LAreaSelectionPlugin: TAreaSelectionPlugin; 577 | begin 578 | if AreasList.ItemIndex = -1 then Exit; 579 | 580 | case Button of 581 | btPrev: 582 | begin 583 | if (AreasList.ItemIndex = (AreasList.Count - 1)) then Exit; 584 | { Move plugin first } 585 | LAreaSelectionPlugin := AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin; 586 | _MoveNextAreaSelectionPlugin(LAreaSelectionPlugin); 587 | { Move item in AreasList then } 588 | LNewStringPos := AreasList.ItemIndex + 1; 589 | AreasList.Items.Move(AreasList.ItemIndex, LNewStringPos); 590 | AreasList.ItemIndex := LNewStringPos; 591 | end; 592 | btNext: 593 | begin 594 | if (AreasList.ItemIndex = 0) then Exit; 595 | { Move plugin first } 596 | LAreaSelectionPlugin := AreasList.Items.Objects[AreasList.ItemIndex] as TAreaSelectionPlugin; 597 | _MovePrevAreaSelectionPlugin(LAreaSelectionPlugin); 598 | { Move item in AreasList then } 599 | LNewStringPos := AreasList.ItemIndex - 1; 600 | AreasList.Items.Move(AreasList.ItemIndex, LNewStringPos); 601 | AreasList.ItemIndex := LNewStringPos; 602 | end; 603 | end; 604 | end; 605 | 606 | procedure TfMain.chkCacheChange(Sender: TObject); 607 | begin 608 | MapView.Engine.ClearCache; 609 | if chkCache.Checked then 610 | begin 611 | MapView.DownloadEngine := MVDECache; 612 | MapView.CacheLocation := clCustom; 613 | MapView.CacheOnDisk := True; 614 | end 615 | else 616 | begin 617 | MapView.DownloadEngine := MVDEFPC; 618 | MapView.CacheOnDisk := False; 619 | end; 620 | MapView.Invalidate; 621 | end; 622 | 623 | procedure TfMain.DirectoryCacheChange(Sender: TObject); 624 | begin 625 | MapView.CacheLocation := clCustom; 626 | MapView.CachePath := DirectoryCache.Text + PathDelim; 627 | end; 628 | 629 | procedure TfMain.FormActivate(Sender: TObject); 630 | begin 631 | MapProvidersToSortedStrings(ProviderVariationsMap.Items); 632 | ReloadLayersList; 633 | end; 634 | 635 | procedure TfMain.FormCreate(Sender: TObject); 636 | begin 637 | FormatSettings.DecimalSeparator := '.'; 638 | FPluginCount := 0; 639 | FPrevAreaIndex.Clear; 640 | DetectAndApplyTheme; 641 | ShowTileInfo := False; 642 | 643 | lblDebugZoom.Caption := SZoom; 644 | lblDebugLat.Caption := SLat; 645 | lblDebugLon.Caption := SLon; 646 | miLanguage.Caption := SLanguage; 647 | end; 648 | 649 | procedure TfMain.LayersListClickCheck(Sender: TObject); 650 | begin 651 | (MapView.Layers.Items[LayersList.ItemIndex] as TMapLayer).Visible := LayersList.Checked[LayersList.ItemIndex]; 652 | end; 653 | 654 | procedure TfMain.LayersListDragDrop(Sender, Source: TObject; X, Y: Integer); 655 | var 656 | clbox : TCheckListBox; 657 | OldIndex, NewIndex: Integer; 658 | begin 659 | if (Sender <> Source) then Exit; // accept dragging only within LayersList 660 | 661 | clbox := Sender as TCheckListBox; 662 | if clbox.Count < 2 then 663 | Exit; 664 | 665 | newIndex := clbox.GetIndexAtXY(X, Y); 666 | oldIndex := clbox.ItemIndex; 667 | 668 | if newIndex = -1 then // if dragging to empty area 669 | newIndex := clbox.Count-1; 670 | 671 | if newIndex = oldIndex then Exit; // Проверка на смену позиции 672 | 673 | clbox.Items.Move(oldIndex, newIndex); // Передвигаем элемент 674 | 675 | clbox.ItemIndex := newIndex; 676 | end; 677 | 678 | procedure TfMain.LayersListDragOver(Sender, Source: TObject; X, Y: Integer; 679 | State: TDragState; var Accept: Boolean); 680 | begin 681 | if LayersList.Count > 0 then 682 | Accept := True; 683 | end; 684 | 685 | procedure TfMain.MapViewCenterMoving(Sender: TObject; 686 | var NewCenter: TRealPoint; var Allow: Boolean); 687 | begin 688 | Allow := True; 689 | end; 690 | 691 | procedure TfMain.MapViewMouseMove(Sender: TObject; Shift: TShiftState; X, 692 | Y: Integer); 693 | begin 694 | UpdateDebugCursorCoordinate(X, Y); 695 | end; 696 | 697 | procedure TfMain.MapViewResize(Sender: TObject); 698 | var 699 | LDistance: Integer; 700 | begin 701 | LDistance := ScaleX(20, 144); // 144 - my work design time 702 | btnZoomIn.Anchors := []; 703 | btnZoomOut.Anchors := []; 704 | btnZoomIn.Left := MapView.Width - btnZoomIn.Width - LDistance; 705 | btnZoomOut.Left := btnZoomIn.Left; 706 | btnZoomIn.Top := Round(MapView.Height / 2) - Round(LDistance / 2) - btnZoomIn.Height; 707 | btnZoomOut.Top := btnZoomIn.Top + LDistance + btnZoomOut.Height; 708 | end; 709 | 710 | procedure TfMain.MapViewZoomChange(Sender: TObject); 711 | begin 712 | UpdateDebugZoom; 713 | end; 714 | 715 | procedure TfMain.miLangaugeEnClick(Sender: TObject); 716 | begin 717 | SetGUILanguage('en'); 718 | end; 719 | 720 | procedure TfMain.miLangaugeRuClick(Sender: TObject); 721 | begin 722 | SetGUILanguage('ru'); 723 | end; 724 | 725 | procedure TfMain.ProviderVariationsMapChange(Sender: TObject); 726 | var 727 | LMinZoom, LMaxZoom: Integer; 728 | begin 729 | with ProviderVariationsMap do 730 | MapView.MapProvider := Items[ItemIndex]; 731 | MapProviderByName(MapView.MapProvider).GetZoomInfos(LMinZoom, LMaxZoom); 732 | MapView.ZoomMin := LMinZoom; 733 | MapView.ZoomMax := LMaxZoom; 734 | MapView.OnMouseMove := @MapViewMouseMove; 735 | MapView.Active := True; 736 | MapViewZoomChange(Self); 737 | end; 738 | 739 | procedure TfMain.LayersUpDownClick(Sender: TObject; Button: TUDBtnType); 740 | var 741 | NewIndex: Integer; 742 | begin 743 | if LayersList.ItemIndex = -1 then Exit; 744 | 745 | case Button of 746 | btNext: 747 | begin 748 | if LayersList.ItemIndex = 0 then Exit; 749 | NewIndex := LayersList.ItemIndex - 1; 750 | MapView.Layers.Move(LayersList.ItemIndex, NewIndex); 751 | LayersList.Items.Move(LayersList.ItemIndex, NewIndex); 752 | LayersList.Selected[NewIndex] := True; 753 | end; 754 | btPrev: 755 | begin 756 | if LayersList.ItemIndex = LayersList.Count-1 then Exit; 757 | NewIndex := LayersList.ItemIndex + 1; 758 | MapView.Layers.Move(LayersList.ItemIndex, NewIndex); 759 | LayersList.Items.Move(LayersList.ItemIndex, NewIndex); 760 | LayersList.Selected[NewIndex] := True; 761 | end; 762 | end; 763 | end; 764 | 765 | procedure TfMain.ProviderVariationsMapSelect(Sender: TObject); 766 | begin 767 | MapView.Active := True; 768 | end; 769 | 770 | procedure TfMain.OnSelectedAreaBeginChange(Sender: TObject); 771 | var 772 | LAreaSelectPlugin: TAreaSelectionPlugin; 773 | LIndex: Integer; 774 | begin 775 | LAreaSelectPlugin := Sender as TAreaSelectionPlugin; 776 | LIndex := AreasList.Items.IndexOfObject(LAreaSelectPlugin); 777 | AreasList.ItemIndex := LIndex; 778 | end; 779 | 780 | procedure TfMain.SetShowTileInfo(AValue: Boolean); 781 | begin 782 | FShowTileInfo := AValue; 783 | 784 | btnTileInfo.ImageIndex := ImgIndTileInfo[FShowTileInfo]; 785 | TileInfoPlugin.Enabled := FShowTileInfo; 786 | end; 787 | 788 | procedure TfMain.ReloadLayersList; 789 | var 790 | il: Integer; 791 | MapLayer: TMapLayer; 792 | begin 793 | LayersList.Clear; 794 | for il := 0 to MapView.Layers.Count-1 do 795 | begin 796 | MapLayer := MapView.Layers[il] as TMapLayer; 797 | LayersList.Items.Add(MapLayer.MapProvider); 798 | if MapLayer.Visible then 799 | LayersList.Checked[il] := True; 800 | end; 801 | end; 802 | 803 | procedure TfMain.DetectAndApplyTheme; 804 | begin 805 | if IsDarkTheme then 806 | ImagesPrimary.Assign(ImagesDark); 807 | end; 808 | 809 | procedure TfMain.UpdateDebugZoom; 810 | begin 811 | lblDebugZoom.Caption := SZoom + ': ' + MapView.Zoom.ToString; 812 | end; 813 | 814 | procedure TfMain.UpdateDebugCursorCoordinate(const X, Y: Integer); 815 | var 816 | LRealPoint: TRealPoint; 817 | LPoint: TPoint; 818 | i: Integer; 819 | begin 820 | LPoint.X := X; LPoint.Y := Y; 821 | LRealPoint := MapView.Engine.ScreenToLatLon(LPoint); 822 | lblDebugLat.Caption := SLat + ': ' + Format('%13.10f', [LRealPoint.Lat]); 823 | lblDebugLon.Caption := SLon + ': ' + Format('%14.10f', [LRealPoint.Lon]); 824 | end; 825 | 826 | procedure TfMain.PrepareAreaSelectionPlugin(AAreaSelectionPlugin: TAreaSelectionPlugin); 827 | begin 828 | AAreaSelectionPlugin.BackgroundColor := BrandColors.Secondary; 829 | AAreaSelectionPlugin.Pen.Color := BrandColors.Accent; 830 | AAreaSelectionPlugin.Font.Color := clWhite; 831 | end; 832 | 833 | procedure TfMain.TranslationChanged; 834 | var 835 | LPoint: TPoint; 836 | begin 837 | LPoint := MapView.ScreenToClient(Mouse.CursorPos); 838 | 839 | miLanguage.Caption := SLanguage; 840 | UpdateDebugZoom; 841 | UpdateDebugCursorCoordinate(LPoint.X, LPoint.Y) 842 | end; 843 | 844 | end. 845 | 846 | -------------------------------------------------------------------------------- /src/core/tildy.core.engine.pas: -------------------------------------------------------------------------------- 1 | { 2 | Copyright (c) 2024-2025 Kirill Filippenok 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. } 15 | 16 | unit Tildy.Core.Engine; 17 | 18 | {$CODEPAGE UTF-8} 19 | {$mode ObjFPC}{$H+}{$MODESWITCH ADVANCEDRECORDS} 20 | 21 | interface 22 | 23 | uses 24 | SysUtils, Classes, StrUtils, Math, FGL, 25 | fphttpclient, openssl, opensslsockets, 26 | BGRABitmap, BGRABitmapTypes; 27 | 28 | const 29 | defOutPath = 'tiles'; 30 | defMinZoom = 6; 31 | defMaxZoom = 7; 32 | defShowFileType = False; 33 | defTileRes = 256; 34 | defOtherTileRes = False; 35 | 36 | type 37 | 38 | IFilter = interface 39 | ['{5DBCB3D5-A14F-48CD-9E72-1F38448C717E}'] 40 | procedure Transform(var ABGRABitmap: TBGRABitmap); 41 | end; 42 | 43 | _TFilters = specialize TFPGMap<String, IFilter>; 44 | 45 | { TFilters } 46 | 47 | TFilters = class(_TFilters) 48 | public 49 | function Add(AKey: String; AFilter: IFilter): Integer; virtual; reintroduce; 50 | function Contains(AKey: String): Boolean; virtual; 51 | end; 52 | 53 | IProjection = interface 54 | ['{1F79F1B6-41F0-4B3F-83DD-0FA312C73BCB}'] 55 | function MinLat: Extended; 56 | function MaxLat: Extended; 57 | function MinLon: Extended; 58 | function MaxLon: Extended; 59 | function CalcTileX(const AZoom: Byte; const ALongitude: Extended): QWord; 60 | function CalcTileY(const AZoom: Byte; const ALatitude: Extended): QWord; 61 | end; 62 | 63 | { TProviderClient } 64 | 65 | TProviderClient = class(TFPCustomHTTPClient) 66 | strict private 67 | FUserAgents: TStringList; 68 | FUserAgent: String; 69 | FUASelected: Boolean; 70 | strict private 71 | procedure SetupUserAgents; virtual; 72 | procedure AutoSelectUserAgent(const AURL: String); virtual; final; 73 | public 74 | constructor Create(AOwner: TComponent); override; 75 | destructor Destroy; override; 76 | public 77 | function ReceiveTile(const AURL: String): TBGRABitmap; 78 | end; 79 | 80 | { IProvider } 81 | 82 | IProvider = interface 83 | ['{9DF4214B-DE9F-4882-8E62-C31AB8F51A1C}'] 84 | function GetCachePath: String; 85 | procedure SetCachePath(AValue: String); 86 | function GetUseCacheOnly: Boolean; 87 | procedure SetUseCacheOnly(AValue: Boolean); 88 | function GetName: String; 89 | procedure SetName(AValue: String); 90 | function GetProjection: IProjection; 91 | procedure SetProjection(AValue: IProjection); 92 | 93 | function GiveTile(AZoom: Integer; AX, AY: Integer): TBGRABitmap; 94 | 95 | property CachePath: String read GetCachePath write SetCachePath; 96 | property UseCacheOnly: Boolean read GetUseCacheOnly write SetUseCacheOnly; 97 | property Name: String read GetName write SetName; 98 | property Projection: IProjection read GetProjection write SetProjection; 99 | end; 100 | 101 | EProvider = class(Exception); 102 | 103 | { TProvider } 104 | 105 | TProvider = class(TInterfacedObject, IProvider) 106 | const 107 | defUseCache = False; 108 | defUseCacheOnly = False; 109 | strict private 110 | FCachePath: String; 111 | FUseCache: Boolean; 112 | FUseCacheOnly: Boolean; 113 | FClient: TProviderClient; 114 | FName: String; 115 | FProjection: IProjection; 116 | FURL: String; 117 | strict private 118 | function GetCachePath: String; 119 | procedure SetCachePath(AValue: String); 120 | function GetUseCacheOnly: Boolean; 121 | procedure SetUseCacheOnly(AValue: Boolean); 122 | function GetName: String; 123 | procedure SetName(AValue: String); 124 | function GetProjection: IProjection; 125 | procedure SetProjection(AValue: IProjection); 126 | strict private 127 | function GetTileLink(AZoom: Integer; AX, AY: Integer): String; 128 | function GetTilePath(AZoom: Integer; AX, AY: Integer): String; 129 | public 130 | constructor Create(AName, AURL: String; AProjection: IProjection); virtual; reintroduce; 131 | destructor Destroy; override; 132 | public 133 | function GiveTile(AZoom: Integer; AX, AY: Integer): TBGRABitmap; 134 | public 135 | property CachePath: String read GetCachePath write SetCachePath; 136 | property UseCacheOnly: Boolean read GetUseCacheOnly write SetUseCacheOnly default defUseCacheOnly; 137 | property Name: String read GetName write SetName; 138 | property Projection: IProjection read GetProjection write SetProjection; 139 | property URL: String read FURL write FURL; 140 | end; 141 | 142 | _TProviders = specialize TFPGMap<String, IProvider>; 143 | 144 | { TProviders } 145 | 146 | TProviders = class(_TProviders) 147 | public 148 | function Add(AKey: String; AName, AURL: String; AProjection: IProjection): Integer; virtual; reintroduce; 149 | function Contains(AKey: String): Boolean; virtual; 150 | end; 151 | 152 | ELayer = class(Exception); 153 | 154 | { TLayer } 155 | 156 | TLayer = class 157 | const 158 | defOpacity = 255; 159 | strict private 160 | FBuffer: TBGRABitmap; 161 | FFilter: IFIlter; 162 | FProvider: IProvider; 163 | FOpacity: Byte; 164 | public 165 | constructor Create(AProvider: IProvider); virtual; reintroduce; 166 | destructor Destroy; override; 167 | public 168 | procedure Load(const AZoom, AX, AY: Integer); 169 | procedure ResampleAndPaintTo(var ABGRABitmap: TBGRABitmap); 170 | public 171 | property Buffer: TBGRABitmap read FBuffer write FBuffer; 172 | property Filter: IFilter read FFilter write FFilter; 173 | property Provider: IProvider read FProvider write FProvider; 174 | property Opacity: Byte read FOpacity write FOpacity default defOpacity; 175 | end; 176 | 177 | { TLayers } 178 | 179 | _TLayers = specialize TFPGObjectList<TLayer>; 180 | 181 | TLayers = class(_TLayers) 182 | public 183 | function Add(AProvider: IProvider): Integer; virtual; reintroduce; 184 | procedure Load(const AZoom: Integer; const AX, AY: Integer); virtual; 185 | end; 186 | 187 | { TMonochromes } 188 | 189 | TMonochromes = class 190 | private 191 | FItems: array of TBGRAPixel; 192 | function GetCount: Integer; 193 | function GetItem(Index: Integer): TBGRAPixel; 194 | procedure PutItem(Index: Integer; AValue: TBGRAPixel); 195 | public 196 | constructor Create; virtual; 197 | destructor Destroy; override; 198 | public 199 | function Add(BGRAPixel: TBGRAPixel): Integer; virtual; overload; 200 | { 201 | Supported variations: 202 | #FFFFFF 203 | rgb(255, 255, 255) 204 | rgba(255, 255, 255, 255) 205 | } 206 | function Add(AColorString: String): Integer; virtual; overload; 207 | property Count: Integer read GetCount; 208 | property Items[Index: Integer]: TBGRAPixel read GetItem write PutItem; default; 209 | end; 210 | 211 | ETildyEngine = class(Exception); 212 | ETESave = class(ETildyEngine); 213 | ETEDownload = class(ETildyEngine); 214 | ETEMonochrome = class(ETildyEngine); 215 | 216 | TTile = record 217 | X, Y: Integer; 218 | Zoom: Integer; 219 | end; 220 | 221 | TArea = record 222 | Left : Extended; 223 | Top : Extended; 224 | Right : Extended; 225 | Bottom: Extended; 226 | end; 227 | 228 | TAreaBounds = record 229 | Left : QWord; 230 | Top : QWord; 231 | Right : QWord; 232 | Bottom: QWord; 233 | end; 234 | 235 | TDownloadStatus = ( 236 | dsOK, 237 | dsSkipExist, 238 | dsSkipMonochrome, 239 | dsNotLoaded, 240 | dsNotSaved 241 | ); 242 | 243 | TDownloadInfo = record 244 | Status : TDownloadStatus; 245 | ErrorMsg: String; 246 | end; 247 | 248 | TProgressEvent = procedure (const AZoom, X, Y: QWord; const ACurrentCount, ATotalCount, AZoomCurrentCount, 249 | AZoomTotalCount: QWord; const AMilliSeconds: Int64; const ADownloadInfo: TDownloadInfo) of object; 250 | 251 | { TTildyEngine } 252 | 253 | TTildyEngine = class 254 | const 255 | defPath = 'tiles/{p}/{z}/{x}/{y}'; 256 | defSkipExisting = False; 257 | defSkipMissing = False; 258 | defSkipMonochrome = False; 259 | defShowFileType = False; 260 | defUseOtherTileRes = False; 261 | strict private 262 | FLayers: TLayers; 263 | FMonochromes: TMonochromes; 264 | FPath: String; 265 | FShowFileType: Boolean; 266 | FSkipExisting: Boolean; 267 | FSkipMissing: Boolean; 268 | FSkipMonochrome: Boolean; 269 | FTileRes: Word; 270 | FUseOtherTileRes: Boolean; 271 | strict private // Getters and Setters 272 | FOnProgress: TProgressEvent; 273 | procedure SetTileRes(AValue: Word); 274 | protected 275 | function ProcessPath(const AProviderName: String; const AZoom: Integer; const AX, AY: Integer): String; 276 | function IsMonochrome(var ATileImg: TBGRABitmap): Boolean; 277 | function InMonochromes(var ATileImg: TBGRABitmap): Boolean; 278 | procedure ResizeIfNeeded(var ATileImg: TBGRABitmap); 279 | procedure SaveTile(const ATileImg: TBGRABitmap; AFilePath: String); 280 | public // Calculations 281 | class function CalcRowTilesCount(const AMinX, AMaxX: QWord): QWord; static; 282 | class function CalcColumnTilesCount(const AMinY, AMaxY: QWord): QWord; static; 283 | class function CalcZoomTilesCount(AProjecion: IProjection; const AZoom: Byte; const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float): QWord; static; 284 | class function CalcTotalTilesCount(AProjecion: IProjection; const AMinZoom, AMaxZoom: Byte; const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float): QWord; static; 285 | function CalcAreaBounds(const AZoom: Integer; const AArea: TArea): TAreaBounds; 286 | public // Create and Destroy 287 | constructor Create; virtual; 288 | destructor Destroy; override; 289 | public 290 | function DownloadTile(ATile: TTile): TDownloadInfo; 291 | procedure Download(const AZoom: Integer); virtual; overload; 292 | procedure Download(const AMinZoom, AMaxZoom: Integer); virtual; overload; 293 | procedure Download(const AMinZoom, AMaxZoom: Integer; const AArea: TArea); virtual; overload; 294 | procedure Download(const AMinZoom, AMaxZoom: Integer; const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float); virtual; overload; 295 | public 296 | property Layers : TLayers read FLayers write FLayers; 297 | property Monochromes : TMonochromes read FMonochromes write FMonochromes; 298 | property Path : String read FPath write FPath; 299 | property ShowFileType: Boolean read FShowFileType write FShowFileType default defShowFileType; 300 | property SkipExisting: Boolean read FSkipExisting write FSkipExisting default defSkipExisting; 301 | property SkipMissing : Boolean read FSkipMissing write FSkipMissing default defSkipMissing; 302 | property TileRes : Word read FTileRes write SetTileRes; 303 | property OnProgress : TProgressEvent read FOnProgress write FOnProgress; 304 | end; 305 | 306 | implementation 307 | 308 | uses 309 | ssockets, Tildy.Utilities.Time; 310 | 311 | { TProviderClient } 312 | 313 | procedure TProviderClient.SetupUserAgents; 314 | begin 315 | if not Assigned(FUserAgents) then Exit; 316 | 317 | FUserAgents.Add('Mozilla/5.0 (compatible; fpweb)'); 318 | FUserAgents.Add('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 YaBrowser/24.6.0.0 Safari/537.36\'); 319 | 320 | FUserAgent := FUserAgents[0]; 321 | end; 322 | 323 | procedure TProviderClient.AutoSelectUserAgent(const AURL: String); 324 | var 325 | i: Integer; 326 | begin 327 | for i := 0 to FUserAgents.Count-1 do 328 | try 329 | Self.AddHeader('User-Agent', FUserAgents[i]); 330 | Self.Get(AURL); 331 | FUASelected := True; 332 | Break; 333 | except 334 | on E: Exception do 335 | begin 336 | if i = FUserAgents.Count-1 then 337 | begin 338 | raise; 339 | end 340 | else 341 | Continue; 342 | end; 343 | end; 344 | end; 345 | 346 | constructor TProviderClient.Create(AOwner: TComponent); 347 | begin 348 | inherited Create(AOwner); 349 | 350 | InitSSLInterface; 351 | Self.AllowRedirect := true; 352 | Self.ConnectTimeOut := 10000; 353 | FUserAgents := TStringList.Create; 354 | SetupUserAgents; 355 | FUASelected := False; 356 | end; 357 | 358 | destructor TProviderClient.Destroy; 359 | begin 360 | FreeAndNil(FUserAgents); 361 | 362 | inherited Destroy; 363 | end; 364 | 365 | function TProviderClient.ReceiveTile(const AURL: String): TBGRABitmap; 366 | var 367 | LMemoryStream: TMemoryStream; 368 | begin 369 | Result := nil; 370 | 371 | if not FUASelected then 372 | try 373 | AutoSelectUserAgent(AURL); 374 | except 375 | on E: Exception do 376 | raise; 377 | end; 378 | 379 | try 380 | LMemoryStream := TMemoryStream.Create; 381 | while True do 382 | try 383 | Self.Get(AURL, LMemoryStream); 384 | break; 385 | except 386 | on E: ESocketError do 387 | continue; 388 | end; 389 | LMemoryStream.Position := 0; 390 | Result := TBGRABitmap.Create(LMemoryStream); 391 | LMemoryStream.Free; 392 | except 393 | on E: Exception do 394 | begin 395 | if Assigned(LMemoryStream) then FreeAndNil(LMemoryStream); 396 | if Assigned(Result) then FreeAndNil(Result); 397 | raise Exception.Create('Failed receive file.'); 398 | end; 399 | end; 400 | end; 401 | 402 | function TProvider.GetName: String; 403 | begin 404 | Result := FName; 405 | end; 406 | 407 | procedure TProvider.SetName(AValue: String); 408 | begin 409 | if FName = AValue then Exit; 410 | FName := AValue; 411 | end; 412 | 413 | function TProvider.GetProjection: IProjection; 414 | begin 415 | Result := FProjection; 416 | end; 417 | 418 | procedure TProvider.SetProjection(AValue: IProjection); 419 | begin 420 | if FProjection = AValue then Exit; 421 | FProjection := AValue; 422 | end; 423 | 424 | function TProvider.GetTileLink(AZoom: Integer; AX, AY: Integer): String; 425 | begin 426 | Result := URL; 427 | Result := StringReplace(Result, '{z}', AZoom.ToString, [rfReplaceAll]); 428 | Result := StringReplace(Result, '{x}', AX.ToString, [rfReplaceAll]); 429 | Result := StringReplace(Result, '{y}', AY.ToString, [rfReplaceAll]); 430 | end; 431 | 432 | function TProvider.GetTilePath(AZoom: Integer; AX, AY: Integer): String; 433 | begin 434 | Result := CachePath; 435 | Result := StringReplace(Result, '{p}', Name, [rfReplaceAll]); 436 | Result := StringReplace(Result, '{z}', AZoom.ToString, [rfReplaceAll]); 437 | Result := StringReplace(Result, '{x}', AX.ToString, [rfReplaceAll]); 438 | Result := StringReplace(Result, '{y}', AY.ToString, [rfReplaceAll]); 439 | end; 440 | 441 | function TProvider.GetCachePath: String; 442 | begin 443 | Result := FCachePath; 444 | end; 445 | 446 | procedure TProvider.SetCachePath(AValue: String); 447 | begin 448 | if FCachePath = AValue then Exit; 449 | FUseCache := true; 450 | FCachePath := AValue; 451 | end; 452 | 453 | function TProvider.GetUseCacheOnly: Boolean; 454 | begin 455 | Result := FUseCacheOnly; 456 | end; 457 | 458 | procedure TProvider.SetUseCacheOnly(AValue: Boolean); 459 | begin 460 | if FUseCacheOnly = AValue then Exit; 461 | FUseCacheOnly := AValue; 462 | end; 463 | 464 | constructor TProvider.Create(AName, AURL: String; AProjection: IProjection); 465 | begin 466 | inherited Create; 467 | 468 | FCachePath := ''; 469 | FUseCache := defUseCache; 470 | FUseCacheOnly := defUseCacheOnly; 471 | FName := AName; 472 | FProjection := AProjection; 473 | FUrl := AURL; 474 | FClient := TProviderClient.Create(nil); 475 | end; 476 | 477 | destructor TProvider.Destroy; 478 | begin 479 | FreeAndNil(FClient); 480 | 481 | inherited Destroy; 482 | end; 483 | 484 | function TProvider.GiveTile(AZoom: Integer; AX, AY: Integer): TBGRABitmap; 485 | var 486 | LFilePath: String; 487 | begin 488 | Result := nil; 489 | try 490 | LFilePath := GetTilePath(AZoom, AX, AY); 491 | 492 | if FUseCache and FileExists(LFilePath) then 493 | Result := TBGRABitmap.Create(LFilePath); 494 | 495 | if FUseCacheOnly then 496 | Exit; 497 | 498 | Result := FClient.ReceiveTile(GetTileLink(AZoom, AX, AY)); 499 | except 500 | on E: Exception do 501 | begin 502 | if Assigned(Result) then FreeAndNil(Result); 503 | if FUseCacheOnly then 504 | raise EProvider.Create('The file corresponding to the tile number is missing from the disk.') 505 | else 506 | raise EProvider.Create('An error occurred while downloading'); 507 | end; 508 | end; 509 | end; 510 | 511 | { TFilters } 512 | 513 | function TFilters.Add(AKey: String; AFilter: IFilter): Integer; 514 | begin 515 | Result := inherited Add(AKey, AFilter); 516 | end; 517 | 518 | function TFilters.Contains(AKey: String): Boolean; 519 | begin 520 | Result := IndexOf(AKey) <> -1; 521 | end; 522 | 523 | { TProviders } 524 | 525 | function TProviders.Add(AKey: String; AName, AURL: String; AProjection: IProjection): Integer; 526 | begin 527 | Result := inherited Add(AKey, TProvider.Create(AName, AUrl, AProjection)); 528 | end; 529 | 530 | function TProviders.Contains(AKey: String): Boolean; 531 | begin 532 | Result := IndexOf(AKey) <> -1; 533 | end; 534 | 535 | { TLayer } 536 | 537 | constructor TLayer.Create(AProvider: IProvider); 538 | begin 539 | inherited Create; 540 | 541 | FProvider := AProvider; 542 | FFilter := nil; 543 | FOpacity := defOpacity; 544 | end; 545 | 546 | destructor TLayer.Destroy; 547 | begin 548 | inherited Destroy; 549 | 550 | if Assigned(FBuffer) then 551 | FreeAndNil(FBuffer); 552 | end; 553 | 554 | procedure TLayer.Load(const AZoom: Integer; const AX, AY: Integer); 555 | begin 556 | if Assigned(FBuffer) then 557 | FreeAndNil(FBuffer); 558 | try 559 | FBuffer := Provider.GiveTile(AZoom, AX, AY); 560 | if not Assigned(FBuffer) then 561 | raise ELayer.Create('Layer of ' + Provider.Name + ' did not load.'); 562 | if Assigned(Filter) then 563 | Filter.Transform(FBuffer); 564 | except 565 | on E: Exception do 566 | raise ELayer.Create(E.Message); 567 | end; 568 | end; 569 | 570 | procedure TLayer.ResampleAndPaintTo(var ABGRABitmap: TBGRABitmap); 571 | var 572 | LResampledBuffer: TBGRABitmap; 573 | begin 574 | if not Assigned(FBuffer) then 575 | Exit; 576 | 577 | LResampledBuffer := FBuffer.Resample(ABGRABitmap.Width, ABGRABitmap.Height); 578 | ABGRABitmap.PutImage(0, 0, LResampledBuffer, dmDrawWithTransparency, Opacity); 579 | LResampledBuffer.Free; 580 | end; 581 | 582 | { TLayers } 583 | 584 | function TLayers.Add(AProvider: IProvider): Integer; 585 | begin 586 | Result := inherited Add(Tlayer.Create(AProvider)); 587 | end; 588 | 589 | procedure TLayers.Load(const AZoom: Integer; const AX, AY: Integer); 590 | var 591 | Layer: TLayer; 592 | begin 593 | for Layer in Self do 594 | Layer.Load(AZoom, AX, AY); 595 | end; 596 | 597 | { TMonochromes } 598 | 599 | function TMonochromes.GetItem(Index: Integer): TBGRAPixel; 600 | begin 601 | Result := FItems[Index]; 602 | end; 603 | 604 | function TMonochromes.GetCount: Integer; 605 | begin 606 | Result := Length(FItems); 607 | end; 608 | 609 | procedure TMonochromes.PutItem(Index: Integer; AValue: TBGRAPixel); 610 | begin 611 | FItems[Index] := AValue; 612 | end; 613 | 614 | constructor TMonochromes.Create; 615 | begin 616 | SetLength(FItems, 0); 617 | end; 618 | 619 | destructor TMonochromes.Destroy; 620 | begin 621 | inherited Destroy; 622 | 623 | SetLength(FItems, 0); 624 | end; 625 | 626 | function TMonochromes.Add(BGRAPixel: TBGRAPixel): Integer; 627 | begin 628 | SetLength(FItems, Succ(Count)); 629 | FItems[Pred(Count)] := BGRAPixel; 630 | end; 631 | 632 | function TMonochromes.Add(AColorString: String): Integer; 633 | var 634 | LBGRAPixel: TBGRAPixel; 635 | begin 636 | LBGRAPixel.FromString(AColorString); 637 | 638 | if LBGRAPixel = BGRAPixelTransparent then 639 | raise Exception.Create('Incorrect color string'); 640 | 641 | Result := Add(LBGRAPixel); 642 | end; 643 | 644 | { TTildyEngine } 645 | 646 | function TTildyEngine.ProcessPath(const AProviderName: String; 647 | const AZoom: Integer; const AX, AY: Integer): String; 648 | begin 649 | Result := Path; 650 | Result := StringReplace(Result, '{p}', AProviderName, [rfReplaceAll]); 651 | Result := StringReplace(Result, '{z}', AZoom.ToString, [rfReplaceAll]); 652 | Result := StringReplace(Result, '{x}', AX.ToString, [rfReplaceAll]); 653 | Result := StringReplace(Result, '{y}', AY.ToString, [rfReplaceAll]); 654 | Result := Result + IfThen(ShowFileType, '.png'); 655 | end; 656 | 657 | function TTildyEngine.IsMonochrome(var ATileImg: TBGRABitmap): Boolean; 658 | var 659 | ix, iy: Integer; 660 | LPrevPixel, LCurrentPixel: TBGRAPixel; 661 | begin 662 | Result := True; 663 | for ix := 0 to Pred(ATileImg.Width) do 664 | for iy := 0 to Pred(ATileImg.Height) do 665 | begin 666 | LCurrentPixel := ATileImg.GetPixel(ix, iy);; 667 | if (ix = 0) and (iy = 0) then 668 | LPrevPixel := LCurrentPixel; 669 | if LPrevPixel <> LCurrentPixel then 670 | Exit(False); 671 | LPrevPixel := LCurrentPixel; 672 | end; 673 | end; 674 | 675 | function TTildyEngine.InMonochromes(var ATileImg: TBGRABitmap): Boolean; 676 | var 677 | i: Integer; 678 | LFirstTilePixel, LMonochromePixel: TBGRAPixel; 679 | begin 680 | Result := False; 681 | 682 | if (ATileImg.Width = 0) or (ATileImg.Height = 0) then 683 | Exit; 684 | 685 | LFirstTilePixel := ATileImg.GetPixel(0, 0); 686 | for i := 0 to Pred(FMonochromes.Count) do 687 | begin 688 | LMonochromePixel := FMonochromes.Items[i]; 689 | if LMonochromePixel = LFirstTilePixel then 690 | Exit(True); 691 | end; 692 | end; 693 | 694 | procedure TTildyEngine.ResizeIfNeeded(var ATileImg: TBGRABitmap); 695 | var 696 | OldTileImg: TBGRABitmap; 697 | begin 698 | if not FUseOtherTileRes then Exit; 699 | if not Assigned(ATileImg) then Exit; 700 | if (ATileImg.Width = TileRes) and (ATileImg.Height = TileRes) then Exit; 701 | 702 | OldTileImg := ATileImg; 703 | ATileImg := ATileImg.Resample(TileRes, TileRes); 704 | OldTileImg.Free; 705 | end; 706 | 707 | procedure TTildyEngine.SaveTile(const ATileImg: TBGRABitmap; AFilePath: String); 708 | var 709 | LFileStream: TFileStream = nil; 710 | begin 711 | if not ForceDirectories(ExtractFilePath(AFilePath)) then 712 | raise ETESave.Create('Failed create dirs.'); 713 | try 714 | LFileStream := TFileStream.Create(AFilePath, fmCreate or fmOpenWrite); 715 | ATileImg.SaveToStreamAsPng(LFileStream); 716 | FreeAndNil(LFileStream); 717 | except 718 | on E: Exception do 719 | begin 720 | if Assigned(LFileStream) then FreeAndNil(LFileStream); 721 | raise ETESave.Create('Failed save file with error: ' + E.Message); 722 | end; 723 | end; 724 | end; 725 | 726 | procedure TTildyEngine.SetTileRes(AValue: Word); 727 | begin 728 | FUseOtherTileRes := True; 729 | if FTileRes = AValue then Exit; 730 | FTileRes := AValue; 731 | end; 732 | 733 | class function TTildyEngine.CalcRowTilesCount(const AMinX, AMaxX: QWord): QWord; 734 | begin 735 | Result := AMaxX - AMinX + 1; 736 | end; 737 | 738 | class function TTildyEngine.CalcColumnTilesCount(const AMinY, AMaxY: QWord): QWord; 739 | begin 740 | Result := AMaxY - AMinY + 1; 741 | end; 742 | 743 | class function TTildyEngine.CalcZoomTilesCount(AProjecion: IProjection; const AZoom: Byte; 744 | const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float): QWord; 745 | var 746 | MinX, MaxX, MinY, MaxY: QWord; 747 | begin 748 | MinX := AProjecion.CalcTileX(AZoom, AMinLongitude); 749 | MaxX := AProjecion.CalcTileX(AZoom, AMaxLongitude); 750 | MinY := AProjecion.CalcTileY(AZoom, AMaxLatitude); 751 | MaxY := AProjecion.CalcTileY(AZoom, AMinLatitude); 752 | Result := CalcRowTilesCount(MinX, MaxX) * CalcColumnTilesCount(MinY, MaxY); 753 | end; 754 | 755 | class function TTildyEngine.CalcTotalTilesCount(AProjecion: IProjection; const AMinZoom, AMaxZoom: Byte; 756 | const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float): QWord; 757 | var 758 | iz: Byte; 759 | begin 760 | Result := 0; 761 | for iz := AMinZoom to AMaxZoom do 762 | Result := Result + CalcZoomTilesCount(AProjecion, iz, AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude); 763 | end; 764 | 765 | function TTildyEngine.CalcAreaBounds(const AZoom: Integer; const AArea: TArea): TAreaBounds; 766 | begin 767 | Result := Default(TAreaBounds); 768 | 769 | if FLayers.Count = 0 then Exit; 770 | 771 | Result.Left := FLayers[0].Provider.Projection.CalcTileY(AZoom, AArea.Left); 772 | Result.Top := FLayers[0].Provider.Projection.CalcTileY(AZoom, AArea.Top); 773 | Result.Right := FLayers[0].Provider.Projection.CalcTileY(AZoom, AArea.Right); 774 | Result.Bottom := FLayers[0].Provider.Projection.CalcTileY(AZoom, AArea.Bottom); 775 | end; 776 | 777 | constructor TTildyEngine.Create; 778 | begin 779 | inherited Create; 780 | 781 | FLayers := TLayers.Create(True); 782 | FMonochromes := TMonochromes.Create; 783 | FUseOtherTileRes := defUseOtherTileRes; 784 | FPath := defPath; 785 | FShowFileType := defShowFileType; 786 | FSkipExisting := defSkipExisting; 787 | FSkipMissing := defSkipMissing; 788 | FSkipMonochrome := defSkipMonochrome; 789 | end; 790 | 791 | destructor TTildyEngine.Destroy; 792 | begin 793 | inherited Destroy; 794 | 795 | FreeAndNil(FLayers); 796 | FreeAndNil(FMonochromes); 797 | end; 798 | 799 | function TTildyEngine.DownloadTile(ATile: TTile): TDownloadInfo; 800 | var 801 | LBuffer : TBGRABitmap = nil; 802 | il : Integer; 803 | LSavePath: String; 804 | begin 805 | Result.Status := dsOK; 806 | Result.ErrorMsg := String.Empty; 807 | 808 | LSavePath := ProcessPath(Layers[0].Provider.Name, ATile.Zoom, ATile.X, ATile.Y); 809 | 810 | if (SkipExisting and FileExists(LSavePath)) then 811 | begin 812 | Result.Status := dsSkipExist; 813 | Exit; 814 | end; 815 | 816 | try 817 | try 818 | if Assigned(LBuffer) then FreeAndNil(LBuffer); 819 | for il := 0 to Layers.Count-1 do 820 | begin 821 | Layers[il].Load(ATile.Zoom, ATile.X, ATile.Y); 822 | if not Assigned(LBuffer) then 823 | begin 824 | LBuffer := Layers[il].Buffer.Duplicate(True); 825 | ResizeIfNeeded(LBuffer); 826 | Continue; 827 | end 828 | else 829 | Layers[il].ResampleAndPaintTo(LBuffer); 830 | end; 831 | 832 | if FSkipMonochrome and IsMonochrome(LBuffer) and InMonochromes(LBuffer) then 833 | raise ETEMonochrome.Create(''); 834 | 835 | SaveTile(LBuffer, LSavePath); 836 | FreeAndNil(LBuffer); 837 | except 838 | on E: Exception do 839 | Result.ErrorMsg := E.Message; 840 | on E: ELayer do 841 | Result.Status := dsNotLoaded; 842 | on E: ETESave do 843 | Result.Status := dsNotSaved; 844 | on E: ETEMonochrome do 845 | Result.Status := dsSkipMonochrome; 846 | end; 847 | finally 848 | FreeAndNil(LBuffer); 849 | end; 850 | end; 851 | 852 | procedure TTildyEngine.Download(const AZoom: Integer); 853 | begin 854 | if FLayers.Count < 1 then Exit; 855 | 856 | Download(AZoom, AZoom); 857 | end; 858 | 859 | procedure TTildyEngine.Download(const AMinZoom, AMaxZoom: Integer); 860 | var 861 | LMainProjection: IProjection; 862 | begin 863 | if FLayers.Count < 1 then Exit; 864 | 865 | LMainProjection := FLayers[0].Provider.Projection; 866 | Download(AMinZoom, AMaxZoom, LMainProjection.MinLat, LMainProjection.MaxLat, LMainProjection.MinLon, LMainProjection.MaxLon); 867 | end; 868 | 869 | procedure TTildyEngine.Download(const AMinZoom, AMaxZoom: Integer; 870 | const AArea: TArea); 871 | begin 872 | Download(AMinZoom, AMaxZoom, AArea.Left, AArea.Right, AArea.Top, AArea.Bottom); 873 | end; 874 | 875 | procedure TTildyEngine.Download(const AMinZoom, AMaxZoom: Integer; const AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude: Float); 876 | var 877 | MinX, MaxX, MinY, MaxY: QWord; 878 | iz: Byte; 879 | ix, iy: Longword; 880 | LZoomCurrentCount, LZoomTotalCount, LCurrentCount, LTotalCount: QWord; 881 | LMainProjection: IProjection; 882 | LBeginTime, LEndTime: Int64; 883 | LTile: TTile; 884 | LDownloadInfo: TDownloadInfo; 885 | begin 886 | if FLayers.Count = 0 then Exit; 887 | 888 | LMainProjection := FLayers[0].Provider.Projection; 889 | LTotalCount := CalcTotalTilesCount(LMainProjection, AMinZoom, AMaxZoom, AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude); 890 | LCurrentCount := 0; 891 | 892 | FSkipMonochrome := Monochromes.Count > 0; 893 | 894 | for iz := AMinZoom to AMaxZoom do 895 | begin 896 | LZoomTotalCount := CalcZoomTilesCount(LMainProjection, iz, AMinLatitude, AMaxLatitude, AMinLongitude, AMaxLongitude); 897 | LZoomCurrentCount := 0; 898 | MinX := LMainProjection.CalcTileX(iz, AMinLongitude); 899 | MaxX := LMainProjection.CalcTileX(iz, AMaxLongitude); 900 | for ix := MinX to MaxX do 901 | begin 902 | MinY := LMainProjection.CalcTileY(iz, AMaxLatitude); 903 | MaxY := LMainProjection.CalcTileY(iz, AMinLatitude); 904 | for iy := MinY to MaxY do 905 | begin 906 | LTile.X := ix; 907 | LTile.Y := iy; 908 | LTile.Zoom := iz; 909 | 910 | LBeginTime := GetTickCountMS; 911 | LDownloadInfo := DownloadTile(LTile); 912 | LEndTime := GetTickCountMS; 913 | 914 | if (LDownloadInfo.Status in [dsOK, dsSkipExist]) then 915 | begin 916 | Inc(LCurrentCount); 917 | Inc(LZoomCurrentCount); 918 | end; 919 | 920 | if Assigned(FOnProgress) then 921 | FOnProgress(iz, ix, iy, LCurrentCount, LTotalCount, LZoomCurrentCount, LZoomTotalCount, LEndTime - LBeginTime, LDownloadInfo); 922 | 923 | case LDownloadInfo.Status of 924 | dsNotLoaded: 925 | if not SkipMissing then ETEDownload.Create('Error occured while downloading.'); 926 | dsNotSaved: ETESave.Create('Error occured while downloading.'); 927 | end; 928 | end; 929 | end; 930 | end; 931 | end; 932 | 933 | end. 934 | 935 | 936 | 937 | --------------------------------------------------------------------------------