Last active
September 6, 2018 08:43
-
-
Save ProgerXP/6011495 to your computer and use it in GitHub Desktop.
Map grabber :: Yandex.Maps + jQuery + PHP/DOMDocument/XPathby Proger_XP :: http://proger.meOriginal article (Russian): http://habrahabr.ru/post/184334/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Map grabber :: Yandex.Maps + jQuery + PHP/DOMDocument/XPath | |
// by Proger_XP :: http://proger.me | |
// Original article (Russian): http://habrahabr.ru/post/184334/ | |
// In public domain. I appreciate backlinks. | |
function dl($url) { | |
require_once 'sqobot/lib/downwind.php'; | |
return new Downwind($url, array( | |
'User-Agent' => 'Mozilla/5.0 (rv:29.0) Gecko/20100101 Firefox/29.0', | |
'Accept-Language' => 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', | |
'Accept-Encoding' => 'gzip, deflate', | |
'Accept-Charset' => 'windows-1251 utf-8 koi8-u big-5 euc-jp shift-jis iso-8859-1', | |
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | |
)); | |
} | |
function parse($html) { | |
$html = '<?xml encoding="UTF-8">'.iconv('cp1251', 'utf-8', $html); | |
$doc = new DOMDocument('1.0', 'utf-8'); | |
@$doc->loadHTML($html) or die('loadHTML: '.$html); | |
//die($doc->saveHTML()); | |
$xpath = new DOMXPath($doc); | |
$nodes = $xpath->query('//table[@class="results"]/tr[th[@class="head_kvart"] or td[@width or @class="tooltip"]]'); | |
$results = array(); | |
$roomCount = 1; | |
foreach ($nodes as $row) { | |
$cells = array(); | |
$cell = $row->firstChild; | |
while ($cell) { | |
$cell->nodeType == XML_ELEMENT_NODE and $cells[] = trim($cell->nodeValue); | |
$cell = $cell->nextSibling; | |
} | |
if (count($cells) == 1) { | |
$roomCount = (int) reset($cells); | |
} else { | |
$cells[0] = $roomCount; | |
$html = $row->ownerDocument->saveXML($row); | |
if (count($cells) == 10) { | |
array_splice($cells, 6, 1, array(0, '', $cells[6], '')); | |
} | |
if (preg_match('~<a href="([^"]+)~u', $html, $match)) { | |
array_unshift($cells, 'http://www.bn.ru'.$match[1]); | |
} else { | |
array_unshift($cells, ''); | |
} | |
$keys = array('url', 'rooms', 'address', 'floors', 'houseType', 'area', 'areaLiving', 'areaKitchen', 'toilet', 'price', 'conditions', 'seller', 'phone', 'notes'); | |
$result[] = array_combine($keys, $cells); | |
} | |
} | |
return $result; | |
} | |
$regions = array( | |
1 => 'Адмиралтейский', | |
6 => 'Василеостровский', | |
7 => 'Выборгский', | |
39 => 'Калининский', | |
8 => 'Кировский', | |
271 => 'Колпинский', | |
40 => 'Красногвардейский', | |
41 => 'Красносельский', | |
272 => 'Кронштадтский', | |
273 => 'Курортный', | |
42 => ' Московский', | |
43 => 'Невский', | |
100 => 'Область', | |
44 => 'Петроградский', | |
275 => 'Петродворцовый', | |
45 => 'Приморский', | |
276 => 'Пушкинский', | |
46 => 'Фрунзенский', | |
47 => 'Центральный', | |
); | |
set_time_limit(180); | |
if (!empty($_REQUEST['find'])) { | |
$all = $counts = array(); | |
$baseURL = 'http://www.bn.ru/zap_fl.phtml?print=printall&'; | |
$empty = iconv('utf-8', 'cp1251', 'Количество найденных вариантов 0'); | |
foreach ((array) $_REQUEST['region'] as $region) { | |
$url = $baseURL."region$region=$region&"; | |
foreach (range(0, 10000, 1000) as $price0) { | |
if ($price0 + 1000 >= $_REQUEST['price0'] and | |
$price0 - 1000 <= $_REQUEST['price1']) { | |
$reqURL = $url."price1=$price0&price2=".($price0 + 999); | |
$data = dl($reqURL)->fetchData(); | |
$counts[$price0] = 0; | |
if (!strpos($data, $empty)) { | |
$offers = parse($data); | |
$counts[$price0] = count($offers); | |
$all = array_merge($all, $offers); | |
} | |
usleep(200000); | |
} | |
} | |
} | |
} | |
false and | |
$all = array( 0 => array ( 'url' => 'http://www.bn.ru/detail/flats/17469.html?from=search', 'rooms' => 1, 'address' => '7 Советская ул. 32/25', 'floors' => '1\\5', 'houseType' => 'СФ', 'area' => '30', 'areaLiving' => '18.3', 'areaKitchen' => '6', 'toilet' => ' ', 'price' => '3100', 'conditions' => ' ', 'seller' => 'АмпиР Недвижимость', 'phone' => '(965) 0165440', 'notes' => 'ПП свободна ХС торг', ), 1 => array ( 'url' => 'http://www.bn.ru/detail/pflats/168605.html?from=psearch', 'rooms' => 1, 'address' => 'Ломоносова ул. 14', 'floors' => '1/6', 'houseType' => 'К', 'area' => '38', 'areaLiving' => '36', 'areaKitchen' => 0, 'toilet' => '', 'price' => '3300 тыс.руб.', 'conditions' => '', 'seller' => 'частное', 'phone' => '(961) 8114432', 'notes' => 'Две 1-комн. Квартиры продаются в...', ), 2 => array ( 'url' => 'http://www.bn.ru/detail/flats/230375.html?from=search', 'rooms' => 1, 'address' => 'Лиговский пр. 259', 'floors' => '1\\5', 'houseType' => 'К', 'area' => '32', 'areaLiving' => '18', 'areaKitchen' => '6', 'toilet' => 'С', 'price' => '3300', 'conditions' => ' ', 'seller' => 'АН Альтаир', 'phone' => '9626023', 'notes' => '(921) 348-4194 комерц расп', ),); | |
?> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Map grabber</title> | |
<script src="//api-maps.yandex.ru/2.0/?load=package.standard&lang=ru-RU" type="text/javascript"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> | |
<style> | |
html, body, .regions, #map { margin: 0; padding: 0; } | |
form { margin: 1em; } | |
.regions { -moz-column-width: 11em; list-style-position: inside; } | |
p[data-marker] { display: inline-block; margin: 3px; } | |
p[data-marker] img { vertical-align: bottom; height: 24px; } | |
p input[type=submit]:first-child { font-weight: bold; } | |
#map { width: 100%; height: 1000px; } | |
.ymaps-b-balloon__content-body table { | |
border-collapse: collapse; | |
} | |
.ymaps-b-balloon__content-body tr > * { | |
padding: 0 6px; | |
text-align: center; | |
} | |
</style> | |
</head> | |
<body> | |
<form action="magrabx.php" method="get"> | |
<ul class="regions"> | |
<?foreach ($regions as $id => $title) {?> | |
<li><label> | |
<input type="checkbox" name="region[]" value="<?=$id?>" | |
<?=in_array($id, (array) @$_REQUEST['region']) ? 'checked="checked"' : ''?>> | |
<?=$title?> | |
</label></li> | |
<?}?> | |
</ul> | |
<p> | |
<b>Цена:</b> | |
от <input name="price0" value="<?=@$_REQUEST['price0'] ?: 1000?>"> | |
до <input name="price1" value="<?=@$_REQUEST['price1'] ?: 4600?>"> | |
</p> | |
<p> | |
<input type="submit" name="find" value="Найти"> | |
<button type="button" onclick="postJS()">Обновить маркеры</button> | |
<?if (isset($all)) {?> | |
<b><?=count($all)?> квартир</b> (без фильтра по цене) | |
/ <?=count($counts)?> запросов | |
<?}?> | |
</p> | |
<?foreach (array('twirl#blueDotIcon', 'twirl#orangeDotIcon', 'twirl#darkblueDotIcon', 'twirl#pinkDotIcon', 'twirl#whiteDotIcon', 'twirl#greyDotIcon', 'twirl#darkgreenDotIcon', 'twirl#redDotIcon', 'twirl#darkorangeDotIcon', 'twirl#violetDotIcon', 'twirl#greenDotIcon', 'twirl#yellowDotIcon', 'twirl#lightblueDotIcon', 'twirl#brownDotIcon', 'twirl#nightDotIcon', 'twirl#blackDotIcon') as $marker) {?> | |
<p data-marker="<?=$marker?>"> | |
<img src="http://api.yandex.ru/maps/doc/jsapi/2.x/ref/images/styles/dot<?=substr($marker, 6, strpos($marker, 'Dot') - 6)?>.png"> | |
<input placeholder="javascript..." name="marker[<?=$marker?>]" value="<?=htmlspecialchars(@$_REQUEST['marker'][$marker], ENT_COMPAT, 'utf-8')?>"> | |
</p> | |
<?}?> | |
</form> | |
<div id="map"></div> | |
<?if (isset($all)) {?> | |
<script type="text/javascript"> | |
function MultiGeocoder(options) { | |
this._options = options || {}; | |
} | |
MultiGeocoder.prototype.geocode = function (requests, options) { | |
var self = this, | |
opts = ymaps.util.extend({}, self._options, options), | |
size = requests.length, | |
promise = new ymaps.util.Promise(), | |
result = [], | |
geoObjects = new ymaps.GeoObjectArray(); | |
requests.forEach(function (request, index) { | |
ymaps.geocode(request, opts).then( | |
function (response) { | |
var geoObject = response.geoObjects.get(0); | |
geoObject && (result[index] = geoObject); | |
--size || (result.forEach(geoObjects.add, geoObjects), promise.resolve({ geoObjects: geoObjects })); | |
}, | |
function (err) { | |
promise.reject(err); | |
} | |
); | |
}); | |
return promise; | |
}; | |
var coords = [] | |
var info = [] | |
<?foreach ($all as $offer) {?> | |
<?if ($offer['price'] >= @$_REQUEST['price0'] and | |
$offer['price'] <= @$_REQUEST['price1']) {?> | |
coords.push('Санкт-Петербург, <?=$offer['address']?>') | |
info.push(<?=json_encode($offer, JSON_UNESCAPED_UNICODE)?>) | |
<?}?> | |
<?}?> | |
function init() { | |
var map = new ymaps.Map('map', { | |
center: [59.932666, 30.329596], | |
zoom: 13, | |
behaviors: ['default', 'scrollZoom'], | |
}) | |
window.map = map | |
map.controls | |
.add('zoomControl', {left: 5, top: 5}) | |
.add('mapTools', {left: 35, top: 5}); | |
coder = new MultiGeocoder({boundedBy: map.getBounds()}); | |
var titles = {price: 'Цена', rooms: 'Комнат', area: 'Площадь', | |
areaLiving: 'Жилая', areaKitchen: 'Кухня', | |
floors: 'Этаж', houseType: 'Дом'} | |
coder | |
.geocode(coords) | |
.then( | |
function (res) { | |
for (var i = 0; i < res.geoObjects.getLength(); i++) { | |
var cells = info[i] | |
var text = '<p style="margin-top: 0">' + $('<b>').append($('<a>').attr({href: cells.url, target: '_blank'}).text(cells.address))[0].outerHTML + '</p>' | |
var th = [] | |
var td = [] | |
for (var key in titles) { | |
th.push('<th>' + titles[key] + '</th>') | |
td.push('<td>' + cells[key] + '</td>') | |
} | |
text += '<table><tr>' + th.join('') + '</tr>' + | |
'<tr>' + td.join('') + '</tr></table>'; | |
text += $('<p style="margin-bottom: 0">').text(cells.notes)[0].outerHTML | |
var geo = res.geoObjects.get(i) | |
info[i].geo = geo | |
geo.properties.set('balloonContentBody', text) | |
geo.events.add('balloonopen', function () { | |
this.options.set('preset', 'twirl#greyIcon') | |
}, geo) | |
} | |
map.geoObjects.add(res.geoObjects) | |
setTimeout(postJS, 100) | |
}, | |
function (err) { alert(err) } | |
) | |
} | |
function postJS() { | |
var markers = [] | |
$('p[data-marker] input').val(function (i, value) { | |
var marker = $(this).parent().attr('data-marker') | |
value = $.trim(value) | |
if (value) { | |
markers.push([marker, value]) | |
} | |
return value | |
}) | |
$.each(info, function (i, cells) { | |
var colored = false | |
for (var i = 0; i < markers.length && !colored; i++) { | |
var item = markers[i] | |
var code = '' | |
$.each(cells, function (key, value) { | |
code += 'var ' + key + ' = \'' + (value + '').replace(/([\r\n\/'])/g, '\\$1') + '\';' | |
}) | |
code += 'return ' + item[1] | |
var func = new Function('cells', code); | |
if (func(cells)) { | |
cells.geo.options.set('preset', item[0]) | |
colored = true | |
} | |
} | |
colored || cells.geo.options.set('preset', 'twirl#blueIcon') | |
}) | |
} | |
ymaps.ready(init); | |
</script> | |
<?}?> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Здравствуйте. Как быть, если количество объектов, которые вернул геокодер не будет совпадать с количеством элементов массива с описанием?