1313use Symfony \Component \Console \Input \InputOption ;
1414use Symfony \Component \Console \Output \OutputInterface ;
1515
16+ /**
17+ * Console command for downloading and converting GeoNames gazetteer data.
18+ *
19+ * This command downloads geographical feature data from the GeoNames database
20+ * and converts it to either JSON format or imports it directly into MongoDB.
21+ *
22+ * Feature classes available for filtering:
23+ * A - Administrative boundaries
24+ * H - Hydrographic features (streams, lakes)
25+ * L - Parks, areas
26+ * P - Populated places (cities, villages)
27+ * R - Roads, railroads
28+ * S - Spots, buildings, farms
29+ * T - Mountains, hills, rocks
30+ * U - Undersea features
31+ * V - Forest, heath
32+ *
33+ * Usage examples:
34+ * geonames:gazetteer:download TH # Download Thailand data
35+ * geonames:gazetteer:download all -c P # All populated places
36+ * geonames:gazetteer:download US -f mongodb # Import US data to MongoDB
37+ */
1638class DownloadGazetteerCommand extends Command
1739{
40+ /**
41+ * Admin code files to clean up after processing.
42+ */
43+ private const ADMIN_CODE_FILES = [
44+ 'admin1CodesASCII.txt ' ,
45+ 'admin2Codes.txt ' ,
46+ ];
47+
48+ /**
49+ * The default command name.
50+ *
51+ * @var string
52+ */
1853 protected static $ defaultName = 'geonames:gazetteer:download ' ;
1954
55+ /**
56+ * The default command description.
57+ *
58+ * @var string
59+ */
2060 protected static $ defaultDescription = 'Download and convert Geonames Gazetteer data ' ;
2161
62+ /**
63+ * The gazetteer downloader instance.
64+ */
2265 private GazetteerDownloader $ downloader ;
2366
67+ /**
68+ * The gazetteer converter instance.
69+ */
2470 private GazetteerConverter $ converter ;
2571
72+ /**
73+ * Create a new download gazetteer command instance.
74+ *
75+ * @param GazetteerDownloader|null $downloader Optional downloader instance for testing
76+ * @param GazetteerConverter|null $converter Optional converter instance for testing
77+ */
2678 public function __construct (?GazetteerDownloader $ downloader = null , ?GazetteerConverter $ converter = null )
2779 {
2880 parent ::__construct ();
@@ -31,6 +83,9 @@ public function __construct(?GazetteerDownloader $downloader = null, ?GazetteerC
3183 $ this ->converter = $ converter ?? new GazetteerConverter ;
3284 }
3385
86+ /**
87+ * Configure the command options and arguments.
88+ */
3489 protected function configure (): void
3590 {
3691 $ this
@@ -43,88 +98,171 @@ protected function configure(): void
4398 ->addOption ('mongodb-collection ' , null , InputOption::VALUE_REQUIRED , 'MongoDB collection name ' , 'gazetteer ' );
4499 }
45100
101+ /**
102+ * Execute the command to download and convert gazetteer data.
103+ *
104+ * @param InputInterface $input The console input interface
105+ * @param OutputInterface $output The console output interface
106+ * @return int The command exit code (SUCCESS or FAILURE)
107+ */
46108 protected function execute (InputInterface $ input , OutputInterface $ output ): int
47109 {
48110 $ country = $ input ->getArgument ('country ' ) ?? 'all ' ;
49111 $ outputDir = $ input ->getOption ('output ' );
50112 $ format = $ input ->getOption ('format ' );
51113
52- // Create output directory if it doesn't exist
53- if (! is_dir ($ outputDir )) {
54- mkdir ($ outputDir , 0777 , true );
55- }
114+ $ this ->ensureOutputDirectoryExists ($ outputDir );
56115
57- // Set output for progress bars
58116 $ this ->downloader ->setOutput ($ output );
59117 $ this ->converter ->setOutput ($ output );
60118
61119 $ output ->writeln ('<info>Downloading Gazetteer data...</info> ' );
62120
63- // Download the data
121+ $ zipFile = $ this ->downloadData ($ country , $ outputDir );
122+
123+ return $ this ->processData ($ input , $ output , $ zipFile , $ format , $ outputDir );
124+ }
125+
126+ /**
127+ * Ensure the output directory exists, creating it if necessary.
128+ *
129+ * @param string $outputDir The output directory path
130+ */
131+ private function ensureOutputDirectoryExists (string $ outputDir ): void
132+ {
133+ if (! is_dir ($ outputDir )) {
134+ mkdir ($ outputDir , 0777 , true );
135+ }
136+ }
137+
138+ /**
139+ * Download gazetteer data for the specified country.
140+ *
141+ * @param string $country The country code or 'all'
142+ * @param string $outputDir The output directory path
143+ * @return string The path to the downloaded ZIP file
144+ */
145+ private function downloadData (string $ country , string $ outputDir ): string
146+ {
64147 if ($ country === 'all ' ) {
65148 $ this ->downloader ->downloadAll ($ outputDir );
66- $ zipFile = $ outputDir .'/allCountries.zip ' ;
67- } else {
68- $ this ->downloader ->download ($ country , $ outputDir );
69- $ zipFile = $ outputDir .'/ ' .strtoupper ($ country ).'.zip ' ;
149+
150+ return $ outputDir .'/allCountries.zip ' ;
70151 }
71152
153+ $ this ->downloader ->download ($ country , $ outputDir );
154+
155+ return $ outputDir .'/ ' .strtoupper ($ country ).'.zip ' ;
156+ }
157+
158+ /**
159+ * Process the downloaded data based on the output format.
160+ *
161+ * @param InputInterface $input The console input interface
162+ * @param OutputInterface $output The console output interface
163+ * @param string $zipFile The path to the ZIP file
164+ * @param string $format The output format (json or mongodb)
165+ * @param string $outputDir The output directory path
166+ * @return int The command exit code
167+ */
168+ private function processData (
169+ InputInterface $ input ,
170+ OutputInterface $ output ,
171+ string $ zipFile ,
172+ string $ format ,
173+ string $ outputDir
174+ ): int {
72175 if ($ format === 'json ' ) {
73- $ output ->writeln ('<info>Converting to JSON format...</info> ' );
74- $ jsonFile = str_replace ('.zip ' , '.json ' , $ zipFile );
75- $ this ->converter ->convert ($ zipFile , $ jsonFile , $ outputDir );
176+ return $ this ->convertToJson ($ output , $ zipFile , $ outputDir );
177+ }
76178
77- // Remove ZIP file after conversion
78- unlink ($ zipFile );
179+ if ($ format === 'mongodb ' ) {
180+ return $ this ->importToMongoDB ($ input , $ output , $ zipFile , $ outputDir );
181+ }
79182
80- // Remove admin code files
81- if (file_exists ($ outputDir .'/admin1CodesASCII.txt ' )) {
82- unlink ($ outputDir .'/admin1CodesASCII.txt ' );
83- }
84- if (file_exists ($ outputDir .'/admin2Codes.txt ' )) {
85- unlink ($ outputDir .'/admin2Codes.txt ' );
86- }
183+ $ output ->writeln (sprintf ('<error>Unsupported format: %s</error> ' , $ format ));
87184
88- $ output ->writeln ('<info>Data has been downloaded and converted successfully!</info> ' );
89- $ output ->writeln (sprintf ('<info>Output file: %s</info> ' , $ jsonFile ));
90- } elseif ($ format === 'mongodb ' ) {
91- $ output ->writeln ('<info>Converting to MongoDB format...</info> ' );
185+ return Command::FAILURE ;
186+ }
92187
93- // Create MongoDB converter
94- $ mongodbUri = $ input ->getOption ('mongodb-uri ' );
95- $ mongodbDb = $ input ->getOption ('mongodb-db ' );
96- $ mongodbCollection = $ input ->getOption ('mongodb-collection ' );
188+ /**
189+ * Convert the gazetteer data to JSON format.
190+ *
191+ * @param OutputInterface $output The console output interface
192+ * @param string $zipFile The path to the ZIP file
193+ * @param string $outputDir The output directory path
194+ * @return int The command exit code
195+ */
196+ private function convertToJson (OutputInterface $ output , string $ zipFile , string $ outputDir ): int
197+ {
198+ $ output ->writeln ('<info>Converting to JSON format...</info> ' );
199+ $ jsonFile = str_replace ('.zip ' , '.json ' , $ zipFile );
200+ $ this ->converter ->convertWithAdminCodes ($ zipFile , $ jsonFile , $ outputDir );
97201
98- $ mongoConverter = new MongoDBGazetteerConverter (
99- $ mongodbUri ,
100- $ mongodbDb ,
101- $ mongodbCollection
102- );
103- $ mongoConverter ->setOutput ($ output );
202+ $ this ->cleanup ($ zipFile , $ outputDir );
104203
105- // Convert and import to MongoDB
106- $ jsonFile = str_replace ('.zip ' , '.json ' , $ zipFile ); // Dummy file name, not used
107- $ mongoConverter ->convert ($ zipFile , $ jsonFile , $ outputDir );
204+ $ output ->writeln ('<info>Data has been downloaded and converted successfully!</info> ' );
205+ $ output ->writeln (sprintf ('<info>Output file: %s</info> ' , $ jsonFile ));
108206
109- // Remove ZIP file after conversion
110- unlink ( $ zipFile );
207+ return Command:: SUCCESS ;
208+ }
111209
112- // Remove admin code files
113- if (file_exists ($ outputDir .'/admin1CodesASCII.txt ' )) {
114- unlink ($ outputDir .'/admin1CodesASCII.txt ' );
115- }
116- if (file_exists ($ outputDir .'/admin2Codes.txt ' )) {
117- unlink ($ outputDir .'/admin2Codes.txt ' );
118- }
210+ /**
211+ * Import the gazetteer data to MongoDB.
212+ *
213+ * @param InputInterface $input The console input interface
214+ * @param OutputInterface $output The console output interface
215+ * @param string $zipFile The path to the ZIP file
216+ * @param string $outputDir The output directory path
217+ * @return int The command exit code
218+ */
219+ private function importToMongoDB (
220+ InputInterface $ input ,
221+ OutputInterface $ output ,
222+ string $ zipFile ,
223+ string $ outputDir
224+ ): int {
225+ $ output ->writeln ('<info>Converting to MongoDB format...</info> ' );
119226
120- $ output ->writeln ('<info>Data has been downloaded and imported to MongoDB successfully!</info> ' );
121- $ output ->writeln (sprintf ('<info>MongoDB: %s.%s</info> ' , $ mongodbDb , $ mongodbCollection ));
122- } else {
123- $ output ->writeln (sprintf ('<error>Unsupported format: %s</error> ' , $ format ));
227+ $ mongodbUri = $ input ->getOption ('mongodb-uri ' );
228+ $ mongodbDb = $ input ->getOption ('mongodb-db ' );
229+ $ mongodbCollection = $ input ->getOption ('mongodb-collection ' );
124230
125- return Command::FAILURE ;
126- }
231+ $ mongoConverter = new MongoDBGazetteerConverter (
232+ $ mongodbUri ,
233+ $ mongodbDb ,
234+ $ mongodbCollection
235+ );
236+ $ mongoConverter ->setOutput ($ output );
237+
238+ $ dummyOutputFile = str_replace ('.zip ' , '.json ' , $ zipFile );
239+ $ mongoConverter ->convertWithAdminCodes ($ zipFile , $ dummyOutputFile , $ outputDir );
240+
241+ $ this ->cleanup ($ zipFile , $ outputDir );
242+
243+ $ output ->writeln ('<info>Data has been downloaded and imported to MongoDB successfully!</info> ' );
244+ $ output ->writeln (sprintf ('<info>MongoDB: %s.%s</info> ' , $ mongodbDb , $ mongodbCollection ));
127245
128246 return Command::SUCCESS ;
129247 }
248+
249+ /**
250+ * Clean up temporary files after processing.
251+ *
252+ * @param string $zipFile The ZIP file to remove
253+ * @param string $outputDir The output directory containing admin code files
254+ */
255+ private function cleanup (string $ zipFile , string $ outputDir ): void
256+ {
257+ if (file_exists ($ zipFile )) {
258+ unlink ($ zipFile );
259+ }
260+
261+ foreach (self ::ADMIN_CODE_FILES as $ file ) {
262+ $ filePath = $ outputDir .'/ ' .$ file ;
263+ if (file_exists ($ filePath )) {
264+ unlink ($ filePath );
265+ }
266+ }
267+ }
130268}
0 commit comments