├── 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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | -
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | -
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
139 |
140 |
141 |
142 |
143 | -
144 |
145 |
146 | -
147 |
148 |
149 | -
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/src/gui/tildy_gui.lpi:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | -
68 |
69 |
70 | -
71 |
72 |
73 | -
74 |
75 |
76 | -
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | -
169 |
170 |
171 | -
172 |
173 |
174 | -
175 |
176 |
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/docs/RESTRICTIONS_RU.md:
--------------------------------------------------------------------------------
1 |
2 | 🇬🇧
English
3 | |
4 | 🇷🇺 Русский
5 |
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 |
2 | 🇬🇧 English
3 | |
4 | 🇷🇺
Русский
5 |
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
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 |
2 | 🇬🇧 English
3 | |
4 | 🇷🇺
Русский
5 |
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 |
2 | 🇬🇧
English
3 | |
4 | 🇷🇺 Русский
5 |
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 |
2 |
3 |
4 |
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;
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;
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;
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;
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 |
--------------------------------------------------------------------------------