1
|
|
|
<?php
|
2
|
|
|
namespace BOTK\Command;
|
3
|
|
|
|
4
|
|
|
use Symfony\Component\Console\Command\Command;
|
5
|
|
|
use Symfony\Component\Console\Input\InputInterface;
|
6
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
7
|
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
8
|
|
|
use Symfony\Component\Console\Question\Question;
|
9
|
|
|
use SKAgarwal\GoogleApi\PlacesApi;
|
10
|
|
|
use BOTK\FactsFactory;
|
11
|
|
|
|
12
|
|
|
class GoogleMapQueryCommand extends Command
|
13
|
|
|
{
|
|
|
|
|
14
|
|
|
protected function configure()
|
15
|
|
|
{
|
16
|
|
|
$this
|
17
|
|
|
->setName('google:places:reasoner')
|
18
|
|
|
->setDescription('Discover information about a local business using Google places APIs.')
|
19
|
|
|
->setHelp('This command search a name in google places returning a ttl file according botk Language profile....')
|
20
|
|
|
->addOption('key','k', InputOption::VALUE_REQUIRED,
|
21
|
|
|
'the mandatory google place api key (see https://developers.google.com/places/web-service/get-api-key)'
|
22
|
|
|
)
|
23
|
|
|
->addOption('namespace','u', InputOption::VALUE_REQUIRED,
|
24
|
|
|
'the namespace for created URI',
|
25
|
|
|
'http://linkeddata.center/resource/'
|
26
|
|
|
)
|
27
|
|
|
->addOption('delay','d', InputOption::VALUE_REQUIRED,
|
28
|
|
|
'delay each call of a fixed amount of seconds',
|
29
|
|
|
0
|
30
|
|
|
)
|
31
|
|
|
->addOption('skip','s', InputOption::VALUE_REQUIRED,
|
32
|
|
|
'number of INPUT lines to skip',
|
33
|
|
|
1
|
34
|
|
|
)
|
35
|
|
|
->addOption('resilience','r', InputOption::VALUE_REQUIRED,
|
36
|
|
|
'max number of errors tolerated before aborting',
|
37
|
|
|
10
|
38
|
|
|
)
|
39
|
|
|
->addOption('fields','f', InputOption::VALUE_REQUIRED,
|
40
|
|
|
'detalis level required (none|contact)',
|
41
|
|
|
'contact'
|
42
|
|
|
)
|
43
|
|
|
->addOption('type','t', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
44
|
|
|
'additional RDF type as uri',
|
45
|
|
|
array('http://schema.org/Place')
|
46
|
|
|
)
|
47
|
|
|
->addOption('assert','a', InputOption::VALUE_REQUIRED,
|
48
|
|
|
'asserted link predicate (sameAs|similarTo)',
|
49
|
|
|
'similarTo'
|
50
|
|
|
)
|
51
|
|
|
->addOption('limit','l', InputOption::VALUE_REQUIRED,
|
52
|
|
|
'max number of calls to google APIs',
|
53
|
|
|
4000
|
54
|
|
|
);
|
55
|
|
|
}
|
56
|
|
|
|
57
|
|
|
|
58
|
|
|
protected function execute(InputInterface $input, OutputInterface $output)
|
59
|
|
|
{
|
60
|
|
|
//cache input parameters
|
61
|
|
|
$uriNameSpace = $input->getOption('namespace');
|
62
|
|
|
$limit = $input->getOption('limit');
|
63
|
|
|
$sleepTime = $input->getOption('delay');
|
64
|
|
|
$detailLevel = $input->getOption('fields');
|
65
|
|
|
$resilience = $input->getOption('resilience');
|
66
|
|
|
$types = $input->getOption('type');
|
67
|
|
|
$similarityPredicate = $input->getOption('assert');
|
68
|
|
|
|
69
|
|
|
if( !($key = $input->getOption('key'))){
|
70
|
|
|
|
71
|
|
|
$helper = $this->getHelper('question');
|
72
|
|
|
$question = new Question('Please enter your google place api key: ');
|
73
|
|
|
$question->setValidator(function ($value) {
|
74
|
|
|
if (trim($value) == '') {
|
75
|
|
|
throw new \Exception('The key cannot be empty');
|
76
|
|
|
}
|
77
|
|
|
|
78
|
|
|
return $value;
|
79
|
|
|
});
|
80
|
|
|
$question->setHidden(true);
|
81
|
|
|
$question->setMaxAttempts(20);
|
82
|
|
|
|
83
|
|
|
$key = $helper->ask($input, $output, $question);
|
84
|
|
|
}
|
85
|
|
|
|
86
|
|
|
$googlePlaces = new PlacesApi($key);
|
87
|
|
|
$factsFactory = new FactsFactory( array(
|
88
|
|
|
'model' => 'LocalBusiness',
|
89
|
|
|
'modelOptions' => array(
|
90
|
|
|
// override the default lowercase filter for id because placeId is case sensitive
|
91
|
|
|
'id' => array('filter'=> FILTER_DEFAULT)
|
92
|
|
|
)
|
93
|
|
|
));
|
94
|
|
|
|
95
|
|
|
// print turtle prefixes
|
96
|
|
|
echo $factsFactory->generateLinkedDataHeader();
|
97
|
|
|
|
98
|
|
|
$lineCount=$callErrorCount = $consecutiveErrorsCount = $callCount = 0;
|
99
|
|
|
|
100
|
|
|
// skip input headers
|
101
|
|
|
for ($i = 0; $i < $input->getOption('skip'); $i++) {
|
102
|
|
|
$lineCount++;
|
103
|
|
|
$output->writeln("<info># Ignored header $lineCount: ". trim(fgets(STDIN)) . '</info>');
|
104
|
|
|
}
|
105
|
|
|
|
106
|
|
|
// main input loop
|
107
|
|
|
while( ($rawData= fgetcsv(STDIN)) && ($callCount <$limit) ){
|
108
|
|
|
$lineCount++;
|
109
|
|
|
if(!is_array($rawData) || (count($rawData)!=2)) {
|
110
|
|
|
$output->writeln("<error># Ignored invalid row at line $lineCount.</error>");
|
111
|
|
|
continue;
|
112
|
|
|
}
|
113
|
|
|
list($uri, $query) = $rawData;
|
114
|
|
|
|
115
|
|
|
|
116
|
|
|
//--------------------------------------------------------------------------------
|
117
|
|
|
// call google place textSearch api, tolerating some errors.
|
118
|
|
|
//--------------------------------------------------------------------------------
|
119
|
|
|
try {
|
120
|
|
|
$searchResultsCollection=$googlePlaces->textSearch($query, array('region'=>'IT'));
|
121
|
|
|
$consecutiveErrorsCount=0;
|
122
|
|
|
$callCount++;
|
123
|
|
|
} catch (\Exception $e) {
|
124
|
|
|
$consecutiveErrorsCount++;$callErrorCount++;
|
125
|
|
|
if( $consecutiveErrorsCount > $resilience ){
|
126
|
|
|
throw $e;
|
127
|
|
|
}
|
128
|
|
|
$messageString = trim(preg_replace('/\s+/', ' ', $e->getMessage()));
|
129
|
|
|
$output->writeln("<error># Ignored Search Api ERROR ($consecutiveErrorsCount): $messageString</error>");
|
130
|
|
|
continue;
|
131
|
|
|
}
|
132
|
|
|
|
133
|
|
|
// skip empty results
|
134
|
|
|
if ($googlePlaces->getStatus()==='ZERO_RESULTS'){
|
135
|
|
|
$output->writeln("<info># no results for '$query'.</info>");
|
136
|
|
|
continue;
|
137
|
|
|
}
|
138
|
|
|
|
139
|
|
|
$output->writeln("<info># discovered data for '$query'.</info>");
|
140
|
|
|
// factualize textSearch results
|
141
|
|
|
$result =$searchResultsCollection['results']->first();
|
142
|
|
|
$placeId = $result['place_id'];
|
143
|
|
|
$data['id'] = $placeId;
|
|
|
|
|
144
|
|
|
$data['uri'] = $uriNameSpace . $placeId;
|
|
|
|
|
145
|
|
|
$data['businessType'] = $types;
|
146
|
|
|
$data[$similarityPredicate] = $uri;
|
147
|
|
|
|
148
|
|
|
if( isset($result['geometry']['location'])) {
|
149
|
|
|
$data['lat'] = $result['geometry']['location']['lat'];
|
150
|
|
|
$data['long'] = $result['geometry']['location']['lng'];
|
151
|
|
|
}
|
152
|
|
|
if( isset($result['formatted_address'])) {
|
153
|
|
|
$data['addressDescription'] = $result['formatted_address'];
|
154
|
|
|
}
|
155
|
|
|
if( isset($result['name'])) {
|
156
|
|
|
$data['businessName'] = $result['name'];
|
157
|
|
|
}
|
158
|
|
|
if( isset($result['types'])) {
|
159
|
|
|
$data['disambiguatingDescription'] = $result['types'];
|
160
|
|
|
}
|
161
|
|
|
|
162
|
|
|
|
163
|
|
|
//--------------------------------------------------------------------------------
|
164
|
|
|
// call google place details api, tolerating some errors.
|
165
|
|
|
//--------------------------------------------------------------------------------
|
166
|
|
|
if ($detailLevel==='contact') {
|
167
|
|
|
try {
|
168
|
|
|
$details=$googlePlaces->placeDetails($placeId, array('region'=>'IT'));
|
169
|
|
|
$consecutiveErrorsCount=0;
|
170
|
|
|
$callCount++;
|
171
|
|
|
} catch (\Exception $e) {
|
172
|
|
|
$consecutiveErrorsCount++;$callErrorCount++;
|
173
|
|
|
if( $consecutiveErrorsCount > $resilience){
|
174
|
|
|
throw $e;
|
175
|
|
|
}
|
176
|
|
|
$messageString = trim(preg_replace('/\s+/', ' ', $e->getMessage()));
|
177
|
|
|
$output->writeln("<error># Ignored Details Api ERROR ($consecutiveErrorsCount): $messageString</error>");
|
178
|
|
|
}
|
179
|
|
|
|
180
|
|
|
// skip empty results
|
181
|
|
|
if ('OK' === $googlePlaces->getStatus()){
|
182
|
|
|
// factualize placeDetails results
|
183
|
|
|
$result =$details['result'];
|
184
|
|
|
if( isset($result['address_components'][1]['short_name']) ) {
|
185
|
|
|
$data['streetAddress'] = $result['address_components'][1]['short_name'];
|
186
|
|
|
}
|
187
|
|
|
if( isset($result['address_components'][0]['short_name']) ) {
|
188
|
|
|
$data['streetAddress'] .= ', ' . $result['address_components'][0]['short_name'];
|
189
|
|
|
}
|
190
|
|
|
if( isset($result['address_components'][3]['short_name']) ) {
|
191
|
|
|
$data['addressLocality'] = $result['address_components'][3]['short_name'];
|
192
|
|
|
}
|
193
|
|
|
if( isset($result['address_components'][0]['short_name']) ) {
|
194
|
|
|
$data['addressRegion'] = $result['address_components'][4]['short_name'];
|
195
|
|
|
}
|
196
|
|
|
if( isset($result['address_components'][5]['short_name']) ) {
|
197
|
|
|
$data['addressRegioneIstat'] = $result['address_components'][5]['short_name'];
|
198
|
|
|
}
|
199
|
|
|
if( isset($result['address_components'][7]['short_name']) ) {
|
200
|
|
|
$data['postalCode'] = $result['address_components'][7]['short_name'];
|
201
|
|
|
}
|
202
|
|
|
if( isset($result['formatted_phone_number']) ) {
|
203
|
|
|
$data['telephone'] = $result['formatted_phone_number'];
|
204
|
|
|
}
|
205
|
|
|
if( isset($result['website']) ) {
|
206
|
|
|
$data['page'] = $result['website'];
|
207
|
|
|
}
|
208
|
|
|
if( isset($result['url']) ) {
|
209
|
|
|
$data['hasMap'] = $result['url'];
|
210
|
|
|
}
|
211
|
|
|
} else {
|
212
|
|
|
$output->writeln("<info># no details for place id '$placeId' details</info>");
|
213
|
|
|
}
|
214
|
|
|
}
|
215
|
|
|
|
216
|
|
|
try {
|
217
|
|
|
$facts =$factsFactory->factualize($data);
|
218
|
|
|
echo $facts->asTurtleFragment(), "\n";
|
219
|
|
|
$droppedFields = $facts->getDroppedFields();
|
220
|
|
|
if(!empty($droppedFields)) {
|
221
|
|
|
$output->writeln("<error># Dropped ".implode(", ", $droppedFields).'</error>');
|
222
|
|
|
$this->factsFactory->addToCounter('error');
|
|
|
|
|
223
|
|
|
}
|
224
|
|
|
} catch (\BOTK\Exception\Warning $e) {
|
225
|
|
|
$output->writeln("<comment># ".$e->getMessage().'</comment>');
|
226
|
|
|
}
|
227
|
|
|
|
228
|
|
|
|
229
|
|
|
sleep($sleepTime);
|
230
|
|
|
}
|
231
|
|
|
|
232
|
|
|
if ($callCount >= $limit && $placeId) {
|
|
|
|
|
233
|
|
|
$output->writeln("<comment># Api call limit reached ($callCount).</comment>");
|
234
|
|
|
}
|
235
|
|
|
|
236
|
|
|
// prints provenances and other metadata
|
237
|
|
|
echo $factsFactory->generateLinkedDataFooter();
|
238
|
|
|
$output->writeln("<info># Called $callCount APIs, $callErrorCount errors.</info>");
|
239
|
|
|
}
|
240
|
|
|
} |