1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5from __future__ import absolute_import, print_function 

6 

7import math 

8import random 

9import logging 

10 

11try: 

12 from StringIO import StringIO as BytesIO 

13except ImportError: 

14 from io import BytesIO 

15 

16import numpy as num 

17 

18from pyrocko.guts import (Object, Float, Bool, Int, Tuple, String, List, 

19 Unicode, Dict) 

20from pyrocko.guts_array import Array 

21from pyrocko.dataset import topo 

22from pyrocko import orthodrome as od 

23from . import gmtpy 

24 

25try: 

26 newstr = unicode 

27except NameError: 

28 newstr = str 

29 

30points_in_region = od.points_in_region 

31 

32logger = logging.getLogger('pyrocko.plot.automap') 

33 

34earthradius = 6371000.0 

35r2d = 180./math.pi 

36d2r = 1./r2d 

37km = 1000. 

38d2m = d2r*earthradius 

39m2d = 1./d2m 

40cm = gmtpy.cm 

41 

42 

43def darken(c, f=0.7): 

44 return (c[0]*f, c[1]*f, c[2]*f) 

45 

46 

47def corners(lon, lat, w, h): 

48 ll_lat, ll_lon = od.ne_to_latlon(lat, lon, -0.5*h, -0.5*w) 

49 ur_lat, ur_lon = od.ne_to_latlon(lat, lon, 0.5*h, 0.5*w) 

50 return ll_lon, ll_lat, ur_lon, ur_lat 

51 

52 

53def extent(lon, lat, w, h, n): 

54 x = num.linspace(-0.5*w, 0.5*w, n) 

55 y = num.linspace(-0.5*h, 0.5*h, n) 

56 slats, slons = od.ne_to_latlon(lat, lon, y[0], x) 

57 nlats, nlons = od.ne_to_latlon(lat, lon, y[-1], x) 

58 south = slats.min() 

59 north = nlats.max() 

60 

61 wlats, wlons = od.ne_to_latlon(lat, lon, y, x[0]) 

62 elats, elons = od.ne_to_latlon(lat, lon, y, x[-1]) 

63 elons = num.where(elons < wlons, elons + 360., elons) 

64 

65 if elons.max() - elons.min() > 180 or wlons.max() - wlons.min() > 180.: 

66 west = -180. 

67 east = 180. 

68 else: 

69 west = wlons.min() 

70 east = elons.max() 

71 

72 return topo.positive_region((west, east, south, north)) 

73 

74 

75class NoTopo(Exception): 

76 pass 

77 

78 

79class OutOfBounds(Exception): 

80 pass 

81 

82 

83class FloatTile(Object): 

84 xmin = Float.T() 

85 ymin = Float.T() 

86 dx = Float.T() 

87 dy = Float.T() 

88 data = Array.T(shape=(None, None), dtype=float, serialize_as='table') 

89 

90 def __init__(self, xmin, ymin, dx, dy, data): 

91 Object.__init__(self, init_props=False) 

92 self.xmin = float(xmin) 

93 self.ymin = float(ymin) 

94 self.dx = float(dx) 

95 self.dy = float(dy) 

96 self.data = data 

97 self._set_maxes() 

98 

99 def _set_maxes(self): 

100 self.ny, self.nx = self.data.shape 

101 self.xmax = self.xmin + (self.nx-1) * self.dx 

102 self.ymax = self.ymin + (self.ny-1) * self.dy 

103 

104 def x(self): 

105 return self.xmin + num.arange(self.nx) * self.dx 

106 

107 def y(self): 

108 return self.ymin + num.arange(self.ny) * self.dy 

109 

110 def get(self, x, y): 

111 ix = int(round((x - self.xmin) / self.dx)) 

112 iy = int(round((y - self.ymin) / self.dy)) 

113 if 0 <= ix < self.nx and 0 <= iy < self.ny: 

114 return self.data[iy, ix] 

115 else: 

116 raise OutOfBounds() 

117 

118 

119class City(Object): 

120 def __init__(self, name, lat, lon, population=None, asciiname=None): 

121 name = newstr(name) 

122 lat = float(lat) 

123 lon = float(lon) 

124 if asciiname is None: 

125 asciiname = name.encode('ascii', errors='replace') 

126 

127 if population is None: 

128 population = 0 

129 else: 

130 population = int(population) 

131 

132 Object.__init__(self, name=name, lat=lat, lon=lon, 

133 population=population, asciiname=asciiname) 

134 

135 name = Unicode.T() 

136 lat = Float.T() 

137 lon = Float.T() 

138 population = Int.T() 

139 asciiname = String.T() 

140 

141 

142class Map(Object): 

143 lat = Float.T(optional=True) 

144 lon = Float.T(optional=True) 

145 radius = Float.T(optional=True) 

146 width = Float.T(default=20.) 

147 height = Float.T(default=14.) 

148 margins = List.T(Float.T()) 

149 illuminate = Bool.T(default=True) 

150 skip_feature_factor = Float.T(default=0.02) 

151 show_grid = Bool.T(default=False) 

152 show_topo = Bool.T(default=True) 

153 show_scale = Bool.T(default=False) 

154 show_topo_scale = Bool.T(default=False) 

155 show_center_mark = Bool.T(default=False) 

156 show_rivers = Bool.T(default=True) 

157 show_plates = Bool.T(default=False) 

158 show_plate_velocities = Bool.T(default=False) 

159 show_plate_names = Bool.T(default=False) 

160 show_boundaries = Bool.T(default=False) 

161 illuminate_factor_land = Float.T(default=0.5) 

162 illuminate_factor_ocean = Float.T(default=0.25) 

163 color_wet = Tuple.T(3, Int.T(), default=(216, 242, 254)) 

164 color_dry = Tuple.T(3, Int.T(), default=(172, 208, 165)) 

165 color_boundaries = Tuple.T(3, Int.T(), default=(1, 1, 1)) 

166 topo_resolution_min = Float.T( 

167 default=40., 

168 help='minimum resolution of topography [dpi]') 

169 topo_resolution_max = Float.T( 

170 default=200., 

171 help='maximum resolution of topography [dpi]') 

172 replace_topo_color_only = FloatTile.T( 

173 optional=True, 

174 help='replace topo color while keeping topographic shading') 

175 topo_cpt_wet = String.T(default='light_sea') 

176 topo_cpt_dry = String.T(default='light_land') 

177 axes_layout = String.T(optional=True) 

178 custom_cities = List.T(City.T()) 

179 gmt_config = Dict.T(String.T(), String.T()) 

180 comment = String.T(optional=True) 

181 approx_ticks = Int.T(default=4) 

182 

183 def __init__(self, gmtversion='newest', **kwargs): 

184 Object.__init__(self, **kwargs) 

185 self._gmt = None 

186 self._scaler = None 

187 self._widget = None 

188 self._corners = None 

189 self._wesn = None 

190 self._minarea = None 

191 self._coastline_resolution = None 

192 self._rivers = None 

193 self._dems = None 

194 self._have_topo_land = None 

195 self._have_topo_ocean = None 

196 self._jxyr = None 

197 self._prep_topo_have = None 

198 self._labels = [] 

199 self._area_labels = [] 

200 self._gmtversion = gmtversion 

201 

202 def save(self, outpath, resolution=75., oversample=2., size=None, 

203 width=None, height=None, psconvert=False, crop_eps_mode=False): 

204 

205 ''' 

206 Save the image. 

207 

208 Save the image to ``outpath``. The format is determined by the filename 

209 extension. Formats are handled as follows: ``'.eps'`` and ``'.ps'`` 

210 produce EPS and PS, respectively, directly with GMT. If the file name 

211 ends with ``'.pdf'``, GMT output is fed through ``gmtpy-epstopdf`` to 

212 create a PDF file. For any other filename extension, output is first 

213 converted to PDF with ``gmtpy-epstopdf``, then with ``pdftocairo`` to 

214 PNG with a resolution oversampled by the factor ``oversample`` and 

215 finally the PNG is downsampled and converted to the target format with 

216 ``convert``. The resolution of rasterized target image can be 

217 controlled either by ``resolution`` in DPI or by specifying ``width`` 

218 or ``height`` or ``size``, where the latter fits the image into a 

219 square with given side length. To save transparency use 

220 ``psconvert=True``. 

221 ''' 

222 

223 gmt = self.gmt 

224 self.draw_labels() 

225 self.draw_axes() 

226 if self.show_topo and self.show_topo_scale: 

227 self._draw_topo_scale() 

228 

229 gmt.save(outpath, resolution=resolution, oversample=oversample, 

230 crop_eps_mode=crop_eps_mode, 

231 size=size, width=width, height=height, psconvert=psconvert) 

232 

233 @property 

234 def scaler(self): 

235 if self._scaler is None: 

236 self._setup_geometry() 

237 

238 return self._scaler 

239 

240 @property 

241 def wesn(self): 

242 if self._wesn is None: 

243 self._setup_geometry() 

244 

245 return self._wesn 

246 

247 @property 

248 def widget(self): 

249 if self._widget is None: 

250 self._setup() 

251 

252 return self._widget 

253 

254 @property 

255 def layout(self): 

256 if self._layout is None: 

257 self._setup() 

258 

259 return self._layout 

260 

261 @property 

262 def jxyr(self): 

263 if self._jxyr is None: 

264 self._setup() 

265 

266 return self._jxyr 

267 

268 @property 

269 def pxyr(self): 

270 if self._pxyr is None: 

271 self._setup() 

272 

273 return self._pxyr 

274 

275 @property 

276 def gmt(self): 

277 if self._gmt is None: 

278 self._setup() 

279 

280 if self._have_topo_ocean is None: 

281 self._draw_background() 

282 

283 return self._gmt 

284 

285 def _setup(self): 

286 if not self._widget: 

287 self._setup_geometry() 

288 

289 self._setup_lod() 

290 self._setup_gmt() 

291 

292 def _setup_geometry(self): 

293 wpage, hpage = self.width, self.height 

294 ml, mr, mt, mb = self._expand_margins() 

295 wpage -= ml + mr 

296 hpage -= mt + mb 

297 

298 wreg = self.radius * 2.0 

299 hreg = self.radius * 2.0 

300 if wpage >= hpage: 

301 wreg *= wpage/hpage 

302 else: 

303 hreg *= hpage/wpage 

304 

305 self._wreg = wreg 

306 self._hreg = hreg 

307 

308 self._corners = corners(self.lon, self.lat, wreg, hreg) 

309 west, east, south, north = extent(self.lon, self.lat, wreg, hreg, 10) 

310 

311 x, y, z = ((west, east), (south, north), (-6000., 4500.)) 

312 

313 xax = gmtpy.Ax(mode='min-max', approx_ticks=self.approx_ticks) 

314 yax = gmtpy.Ax(mode='min-max', approx_ticks=self.approx_ticks) 

315 zax = gmtpy.Ax(mode='min-max', inc=1000., label='Height', 

316 scaled_unit='km', scaled_unit_factor=0.001) 

317 

318 scaler = gmtpy.ScaleGuru(data_tuples=[(x, y, z)], axes=(xax, yax, zax)) 

319 

320 par = scaler.get_params() 

321 

322 west = par['xmin'] 

323 east = par['xmax'] 

324 south = par['ymin'] 

325 north = par['ymax'] 

326 

327 self._wesn = west, east, south, north 

328 self._scaler = scaler 

329 

330 def _setup_lod(self): 

331 w, e, s, n = self._wesn 

332 if self.radius > 1500.*km: 

333 coastline_resolution = 'i' 

334 rivers = False 

335 else: 

336 coastline_resolution = 'f' 

337 rivers = True 

338 

339 self._minarea = (self.skip_feature_factor * self.radius/km)**2 

340 

341 self._coastline_resolution = coastline_resolution 

342 self._rivers = rivers 

343 

344 self._prep_topo_have = {} 

345 self._dems = {} 

346 

347 cm2inch = gmtpy.cm/gmtpy.inch 

348 

349 dmin = 2.0 * self.radius * m2d / (self.topo_resolution_max * 

350 (self.height * cm2inch)) 

351 dmax = 2.0 * self.radius * m2d / (self.topo_resolution_min * 

352 (self.height * cm2inch)) 

353 

354 for k in ['ocean', 'land']: 

355 self._dems[k] = topo.select_dem_names(k, dmin, dmax, self._wesn) 

356 if self._dems[k]: 

357 logger.debug('using topography dataset %s for %s' 

358 % (','.join(self._dems[k]), k)) 

359 

360 def _expand_margins(self): 

361 if len(self.margins) == 0 or len(self.margins) > 4: 

362 ml = mr = mt = mb = 2.0 

363 elif len(self.margins) == 1: 

364 ml = mr = mt = mb = self.margins[0] 

365 elif len(self.margins) == 2: 

366 ml = mr = self.margins[0] 

367 mt = mb = self.margins[1] 

368 elif len(self.margins) == 4: 

369 ml, mr, mt, mb = self.margins 

370 

371 return ml, mr, mt, mb 

372 

373 def _setup_gmt(self): 

374 w, h = self.width, self.height 

375 scaler = self._scaler 

376 

377 if gmtpy.is_gmt5(self._gmtversion): 

378 gmtconf = dict( 

379 MAP_TICK_PEN_PRIMARY='1.25p', 

380 MAP_TICK_PEN_SECONDARY='1.25p', 

381 MAP_TICK_LENGTH_PRIMARY='0.2c', 

382 MAP_TICK_LENGTH_SECONDARY='0.6c', 

383 FONT_ANNOT_PRIMARY='12p,1,black', 

384 FONT_LABEL='12p,1,black', 

385 PS_CHAR_ENCODING='ISOLatin1+', 

386 MAP_FRAME_TYPE='fancy', 

387 FORMAT_GEO_MAP='D', 

388 PS_MEDIA='Custom_%ix%i' % ( 

389 w*gmtpy.cm, 

390 h*gmtpy.cm), 

391 PS_PAGE_ORIENTATION='portrait', 

392 MAP_GRID_PEN_PRIMARY='thinnest,0/50/0', 

393 MAP_ANNOT_OBLIQUE='6') 

394 else: 

395 gmtconf = dict( 

396 TICK_PEN='1.25p', 

397 TICK_LENGTH='0.2c', 

398 ANNOT_FONT_PRIMARY='1', 

399 ANNOT_FONT_SIZE_PRIMARY='12p', 

400 LABEL_FONT='1', 

401 LABEL_FONT_SIZE='12p', 

402 CHAR_ENCODING='ISOLatin1+', 

403 BASEMAP_TYPE='fancy', 

404 PLOT_DEGREE_FORMAT='D', 

405 PAPER_MEDIA='Custom_%ix%i' % ( 

406 w*gmtpy.cm, 

407 h*gmtpy.cm), 

408 GRID_PEN_PRIMARY='thinnest/0/50/0', 

409 DOTS_PR_INCH='1200', 

410 OBLIQUE_ANNOTATION='6') 

411 

412 gmtconf.update( 

413 (k.upper(), v) for (k, v) in self.gmt_config.items()) 

414 

415 gmt = gmtpy.GMT(config=gmtconf, version=self._gmtversion) 

416 

417 layout = gmt.default_layout() 

418 

419 layout.set_fixed_margins(*[x*cm for x in self._expand_margins()]) 

420 

421 widget = layout.get_widget() 

422 widget['P'] = widget['J'] 

423 widget['J'] = ('-JA%g/%g' % (self.lon, self.lat)) + '/%(width)gp' 

424 scaler['R'] = '-R%g/%g/%g/%gr' % self._corners 

425 

426 # aspect = gmtpy.aspect_for_projection( 

427 # gmt.installation['version'], *(widget.J() + scaler.R())) 

428 

429 aspect = self._map_aspect(jr=widget.J() + scaler.R()) 

430 widget.set_aspect(aspect) 

431 

432 self._gmt = gmt 

433 self._layout = layout 

434 self._widget = widget 

435 self._jxyr = self._widget.JXY() + self._scaler.R() 

436 self._pxyr = self._widget.PXY() + [ 

437 '-R%g/%g/%g/%g' % (0, widget.width(), 0, widget.height())] 

438 self._have_drawn_axes = False 

439 self._have_drawn_labels = False 

440 

441 def _draw_background(self): 

442 self._have_topo_land = False 

443 self._have_topo_ocean = False 

444 if self.show_topo: 

445 self._have_topo = self._draw_topo() 

446 

447 self._draw_basefeatures() 

448 

449 def _get_topo_tile(self, k): 

450 t = None 

451 demname = None 

452 for dem in self._dems[k]: 

453 t = topo.get(dem, self._wesn) 

454 demname = dem 

455 if t is not None: 

456 break 

457 

458 if not t: 

459 raise NoTopo() 

460 

461 return t, demname 

462 

463 def _prep_topo(self, k): 

464 gmt = self._gmt 

465 t, demname = self._get_topo_tile(k) 

466 

467 if demname not in self._prep_topo_have: 

468 

469 grdfile = gmt.tempfilename() 

470 

471 is_flat = num.all(t.data[0] == t.data) 

472 

473 gmtpy.savegrd( 

474 t.x(), t.y(), t.data, filename=grdfile, naming='lonlat') 

475 

476 if self.illuminate and not is_flat: 

477 if k == 'ocean': 

478 factor = self.illuminate_factor_ocean 

479 else: 

480 factor = self.illuminate_factor_land 

481 

482 ilumfn = gmt.tempfilename() 

483 gmt.grdgradient( 

484 grdfile, 

485 N='e%g' % factor, 

486 A=-45, 

487 G=ilumfn, 

488 out_discard=True) 

489 

490 ilumargs = ['-I%s' % ilumfn] 

491 else: 

492 ilumargs = [] 

493 

494 if self.replace_topo_color_only: 

495 t2 = self.replace_topo_color_only 

496 grdfile2 = gmt.tempfilename() 

497 

498 gmtpy.savegrd( 

499 t2.x(), t2.y(), t2.data, filename=grdfile2, 

500 naming='lonlat') 

501 

502 if gmt.is_gmt5(): 

503 gmt.grdsample( 

504 grdfile2, 

505 G=grdfile, 

506 n='l', 

507 I='%g/%g' % (t.dx, t.dy), # noqa 

508 R=grdfile, 

509 out_discard=True) 

510 else: 

511 gmt.grdsample( 

512 grdfile2, 

513 G=grdfile, 

514 Q='l', 

515 I='%g/%g' % (t.dx, t.dy), # noqa 

516 R=grdfile, 

517 out_discard=True) 

518 

519 gmt.grdmath( 

520 grdfile, '0.0', 'AND', '=', grdfile2, 

521 out_discard=True) 

522 

523 grdfile = grdfile2 

524 

525 self._prep_topo_have[demname] = grdfile, ilumargs 

526 

527 return self._prep_topo_have[demname] 

528 

529 def _draw_topo(self): 

530 widget = self._widget 

531 scaler = self._scaler 

532 gmt = self._gmt 

533 cres = self._coastline_resolution 

534 minarea = self._minarea 

535 

536 JXY = widget.JXY() 

537 R = scaler.R() 

538 

539 try: 

540 grdfile, ilumargs = self._prep_topo('ocean') 

541 gmt.pscoast(D=cres, S='c', A=minarea, *(JXY+R)) 

542 gmt.grdimage(grdfile, C=topo.cpt(self.topo_cpt_wet), 

543 *(ilumargs+JXY+R)) 

544 gmt.pscoast(Q=True, *(JXY+R)) 

545 self._have_topo_ocean = True 

546 except NoTopo: 

547 self._have_topo_ocean = False 

548 

549 try: 

550 grdfile, ilumargs = self._prep_topo('land') 

551 gmt.pscoast(D=cres, G='c', A=minarea, *(JXY+R)) 

552 gmt.grdimage(grdfile, C=topo.cpt(self.topo_cpt_dry), 

553 *(ilumargs+JXY+R)) 

554 gmt.pscoast(Q=True, *(JXY+R)) 

555 self._have_topo_land = True 

556 except NoTopo: 

557 self._have_topo_land = False 

558 

559 def _draw_topo_scale(self, label='Elevation [km]'): 

560 dry = read_cpt(topo.cpt(self.topo_cpt_dry)) 

561 wet = read_cpt(topo.cpt(self.topo_cpt_wet)) 

562 combi = cpt_merge_wet_dry(wet, dry) 

563 for level in combi.levels: 

564 level.vmin /= km 

565 level.vmax /= km 

566 

567 topo_cpt = self.gmt.tempfilename() + '.cpt' 

568 write_cpt(combi, topo_cpt) 

569 

570 (w, h), (xo, yo) = self.widget.get_size() 

571 self.gmt.psscale( 

572 D='%gp/%gp/%gp/%gph' % (xo + 0.5*w, yo - 2.0*gmtpy.cm, w, 

573 0.5*gmtpy.cm), 

574 C=topo_cpt, 

575 B='1:%s:' % label) 

576 

577 def _draw_basefeatures(self): 

578 gmt = self._gmt 

579 cres = self._coastline_resolution 

580 rivers = self._rivers 

581 minarea = self._minarea 

582 

583 color_wet = self.color_wet 

584 color_dry = self.color_dry 

585 

586 if self.show_rivers and rivers: 

587 rivers = ['-Ir/0.25p,%s' % gmtpy.color(self.color_wet)] 

588 else: 

589 rivers = [] 

590 

591 fill = {} 

592 if not self._have_topo_land: 

593 fill['G'] = color_dry 

594 

595 if not self._have_topo_ocean: 

596 fill['S'] = color_wet 

597 

598 if self.show_boundaries: 

599 fill['N'] = '1/1p,%s,%s' % ( 

600 gmtpy.color(self.color_boundaries), 'solid') 

601 

602 gmt.pscoast( 

603 D=cres, 

604 W='thinnest,%s' % gmtpy.color(darken(gmtpy.color_tup(color_dry))), 

605 A=minarea, 

606 *(rivers+self._jxyr), **fill) 

607 

608 if self.show_plates: 

609 self.draw_plates() 

610 

611 def _draw_axes(self): 

612 gmt = self._gmt 

613 scaler = self._scaler 

614 widget = self._widget 

615 

616 if self.axes_layout is None: 

617 if self.lat > 0.0: 

618 axes_layout = 'WSen' 

619 else: 

620 axes_layout = 'WseN' 

621 else: 

622 axes_layout = self.axes_layout 

623 

624 scale_km = gmtpy.nice_value(self.radius/5.) / 1000. 

625 

626 if self.show_center_mark: 

627 gmt.psxy( 

628 in_rows=[[self.lon, self.lat]], 

629 S='c20p', W='2p,black', 

630 *self._jxyr) 

631 

632 if self.show_grid: 

633 btmpl = ('%(xinc)gg%(xinc)g:%(xlabel)s:/' 

634 '%(yinc)gg%(yinc)g:%(ylabel)s:') 

635 else: 

636 btmpl = '%(xinc)g:%(xlabel)s:/%(yinc)g:%(ylabel)s:' 

637 

638 if self.show_scale: 

639 scale = 'x%gp/%gp/%g/%g/%gk' % ( 

640 6./7*widget.width(), 

641 widget.height()/7., 

642 self.lon, 

643 self.lat, 

644 scale_km) 

645 else: 

646 scale = False 

647 

648 gmt.psbasemap( 

649 B=(btmpl % scaler.get_params())+axes_layout, 

650 L=scale, 

651 *self._jxyr) 

652 

653 if self.comment: 

654 font_size = self.gmt.label_font_size() 

655 

656 _, east, south, _ = self._wesn 

657 if gmt.is_gmt5(): 

658 row = [ 

659 1, 0, 

660 '%gp,%s,%s' % (font_size, 0, 'black'), 'BR', 

661 self.comment] 

662 

663 farg = ['-F+f+j'] 

664 else: 

665 row = [1, 0, font_size, 0, 0, 'BR', self.comment] 

666 farg = [] 

667 

668 gmt.pstext( 

669 in_rows=[row], 

670 N=True, 

671 R=(0, 1, 0, 1), 

672 D='%gp/%gp' % (-font_size*0.2, font_size*0.3), 

673 *(widget.PXY() + farg)) 

674 

675 def draw_axes(self): 

676 if not self._have_drawn_axes: 

677 self._draw_axes() 

678 self._have_drawn_axes = True 

679 

680 def _have_coastlines(self): 

681 gmt = self._gmt 

682 cres = self._coastline_resolution 

683 minarea = self._minarea 

684 

685 checkfile = gmt.tempfilename() 

686 

687 gmt.pscoast( 

688 M=True, 

689 D=cres, 

690 W='thinnest,black', 

691 A=minarea, 

692 out_filename=checkfile, 

693 *self._jxyr) 

694 

695 points = [] 

696 with open(checkfile, 'r') as f: 

697 for line in f: 

698 ls = line.strip() 

699 if ls.startswith('#') or ls.startswith('>') or ls == '': 

700 continue 

701 plon, plat = [float(x) for x in ls.split()] 

702 points.append((plat, plon)) 

703 

704 points = num.array(points, dtype=float) 

705 return num.any(points_in_region(points, self._wesn)) 

706 

707 def have_coastlines(self): 

708 self.gmt 

709 return self._have_coastlines() 

710 

711 def project(self, lats, lons, jr=None): 

712 onepoint = False 

713 if isinstance(lats, float) and isinstance(lons, float): 

714 lats = [lats] 

715 lons = [lons] 

716 onepoint = True 

717 

718 if jr is not None: 

719 j, r = jr 

720 gmt = gmtpy.GMT(version=self._gmtversion) 

721 else: 

722 j, _, _, r = self.jxyr 

723 gmt = self.gmt 

724 

725 f = BytesIO() 

726 gmt.mapproject(j, r, in_columns=(lons, lats), out_stream=f, D='p') 

727 f.seek(0) 

728 data = num.loadtxt(f, ndmin=2) 

729 xs, ys = data.T 

730 if onepoint: 

731 xs = xs[0] 

732 ys = ys[0] 

733 return xs, ys 

734 

735 def _map_box(self, jr=None): 

736 ll_lon, ll_lat, ur_lon, ur_lat = self._corners 

737 

738 xs_corner, ys_corner = self.project( 

739 (ll_lat, ur_lat), (ll_lon, ur_lon), jr=jr) 

740 

741 w = xs_corner[1] - xs_corner[0] 

742 h = ys_corner[1] - ys_corner[0] 

743 

744 return w, h 

745 

746 def _map_aspect(self, jr=None): 

747 w, h = self._map_box(jr=jr) 

748 return h/w 

749 

750 def _draw_labels(self): 

751 points_taken = [] 

752 regions_taken = [] 

753 

754 def no_points_in_rect(xs, ys, xmin, ymin, xmax, ymax): 

755 xx = not num.any(la(la(xmin < xs, xs < xmax), 

756 la(ymin < ys, ys < ymax))) 

757 return xx 

758 

759 def roverlaps(a, b): 

760 return (a[0] < b[2] and b[0] < a[2] and 

761 a[1] < b[3] and b[1] < a[3]) 

762 

763 w, h = self._map_box() 

764 

765 label_font_size = self.gmt.label_font_size() 

766 

767 if self._labels: 

768 

769 n = len(self._labels) 

770 

771 lons, lats, texts, sx, sy, colors, fonts, font_sizes, \ 

772 angles, styles = list(zip(*self._labels)) 

773 

774 font_sizes = [ 

775 (font_size or label_font_size) for font_size in font_sizes] 

776 

777 sx = num.array(sx, dtype=float) 

778 sy = num.array(sy, dtype=float) 

779 

780 xs, ys = self.project(lats, lons) 

781 

782 points_taken.append((xs, ys)) 

783 

784 dxs = num.zeros(n) 

785 dys = num.zeros(n) 

786 

787 for i in range(n): 

788 dx, dy = gmtpy.text_box( 

789 texts[i], 

790 font=fonts[i], 

791 font_size=font_sizes[i], 

792 **styles[i]) 

793 

794 dxs[i] = dx 

795 dys[i] = dy 

796 

797 la = num.logical_and 

798 anchors_ok = ( 

799 la(xs + sx + dxs < w, ys + sy + dys < h), 

800 la(xs - sx - dxs > 0., ys - sy - dys > 0.), 

801 la(xs + sx + dxs < w, ys - sy - dys > 0.), 

802 la(xs - sx - dxs > 0., ys + sy + dys < h), 

803 ) 

804 

805 arects = [ 

806 (xs, ys, xs + sx + dxs, ys + sy + dys), 

807 (xs - sx - dxs, ys - sy - dys, xs, ys), 

808 (xs, ys - sy - dys, xs + sx + dxs, ys), 

809 (xs - sx - dxs, ys, xs, ys + sy + dys)] 

810 

811 for i in range(n): 

812 for ianch in range(4): 

813 anchors_ok[ianch][i] &= no_points_in_rect( 

814 xs, ys, *[xxx[i] for xxx in arects[ianch]]) 

815 

816 anchor_choices = [] 

817 anchor_take = [] 

818 for i in range(n): 

819 choices = [ianch for ianch in range(4) 

820 if anchors_ok[ianch][i]] 

821 anchor_choices.append(choices) 

822 if choices: 

823 anchor_take.append(choices[0]) 

824 else: 

825 anchor_take.append(None) 

826 

827 def cost(anchor_take): 

828 noverlaps = 0 

829 for i in range(n): 

830 for j in range(n): 

831 if i != j: 

832 i_take = anchor_take[i] 

833 j_take = anchor_take[j] 

834 if i_take is None or j_take is None: 

835 continue 

836 r_i = [xxx[i] for xxx in arects[i_take]] 

837 r_j = [xxx[j] for xxx in arects[j_take]] 

838 if roverlaps(r_i, r_j): 

839 noverlaps += 1 

840 

841 return noverlaps 

842 

843 cur_cost = cost(anchor_take) 

844 imax = 30 

845 while cur_cost != 0 and imax > 0: 

846 for i in range(n): 

847 for t in anchor_choices[i]: 

848 anchor_take_new = list(anchor_take) 

849 anchor_take_new[i] = t 

850 new_cost = cost(anchor_take_new) 

851 if new_cost < cur_cost: 

852 anchor_take = anchor_take_new 

853 cur_cost = new_cost 

854 

855 imax -= 1 

856 

857 while cur_cost != 0: 

858 for i in range(n): 

859 anchor_take_new = list(anchor_take) 

860 anchor_take_new[i] = None 

861 new_cost = cost(anchor_take_new) 

862 if new_cost < cur_cost: 

863 anchor_take = anchor_take_new 

864 cur_cost = new_cost 

865 break 

866 

867 anchor_strs = ['BL', 'TR', 'TL', 'BR'] 

868 

869 for i in range(n): 

870 ianchor = anchor_take[i] 

871 color = colors[i] 

872 if color is None: 

873 color = 'black' 

874 

875 if ianchor is not None: 

876 regions_taken.append([xxx[i] for xxx in arects[ianchor]]) 

877 

878 anchor = anchor_strs[ianchor] 

879 

880 yoff = [-sy[i], sy[i]][anchor[0] == 'B'] 

881 xoff = [-sx[i], sx[i]][anchor[1] == 'L'] 

882 if self.gmt.is_gmt5(): 

883 row = ( 

884 lons[i], lats[i], 

885 '%i,%s,%s' % (font_sizes[i], fonts[i], color), 

886 anchor, 

887 texts[i]) 

888 

889 farg = ['-F+f+j+a%g' % angles[i]] 

890 else: 

891 row = ( 

892 lons[i], lats[i], 

893 font_sizes[i], angles[i], fonts[i], anchor, 

894 texts[i]) 

895 farg = ['-G%s' % color] 

896 

897 self.gmt.pstext( 

898 in_rows=[row], 

899 D='%gp/%gp' % (xoff, yoff), 

900 *(self.jxyr + farg), 

901 **styles[i]) 

902 

903 if self._area_labels: 

904 

905 for lons, lats, text, color, font, font_size, style in \ 

906 self._area_labels: 

907 

908 if font_size is None: 

909 font_size = label_font_size 

910 

911 if color is None: 

912 color = 'black' 

913 

914 if self.gmt.is_gmt5(): 

915 farg = ['-F+f+j'] 

916 else: 

917 farg = ['-G%s' % color] 

918 

919 xs, ys = self.project(lats, lons) 

920 dx, dy = gmtpy.text_box( 

921 text, font=font, font_size=font_size, **style) 

922 

923 rects = [xs-0.5*dx, ys-0.5*dy, xs+0.5*dx, ys+0.5*dy] 

924 

925 locs_ok = num.ones(xs.size, dtype=num.bool) 

926 

927 for iloc in range(xs.size): 

928 rcandi = [xxx[iloc] for xxx in rects] 

929 

930 locs_ok[iloc] = True 

931 locs_ok[iloc] &= ( 

932 0 < rcandi[0] and rcandi[2] < w 

933 and 0 < rcandi[1] and rcandi[3] < h) 

934 

935 overlap = False 

936 for r in regions_taken: 

937 if roverlaps(r, rcandi): 

938 overlap = True 

939 break 

940 

941 locs_ok[iloc] &= not overlap 

942 

943 for xs_taken, ys_taken in points_taken: 

944 locs_ok[iloc] &= no_points_in_rect( 

945 xs_taken, ys_taken, *rcandi) 

946 

947 if not locs_ok[iloc]: 

948 break 

949 

950 rows = [] 

951 for iloc, (lon, lat) in enumerate(zip(lons, lats)): 

952 if not locs_ok[iloc]: 

953 continue 

954 

955 if self.gmt.is_gmt5(): 

956 row = ( 

957 lon, lat, 

958 '%i,%s,%s' % (font_size, font, color), 

959 'MC', 

960 text) 

961 

962 else: 

963 row = ( 

964 lon, lat, 

965 font_size, 0, font, 'MC', 

966 text) 

967 

968 rows.append(row) 

969 

970 regions_taken.append([xxx[iloc] for xxx in rects]) 

971 break 

972 

973 self.gmt.pstext( 

974 in_rows=rows, 

975 *(self.jxyr + farg), 

976 **style) 

977 

978 def draw_labels(self): 

979 self.gmt 

980 if not self._have_drawn_labels: 

981 self._draw_labels() 

982 self._have_drawn_labels = True 

983 

984 def add_label( 

985 self, lat, lon, text, 

986 offset_x=5., offset_y=5., 

987 color=None, 

988 font='1', 

989 font_size=None, 

990 angle=0, 

991 style={}): 

992 

993 if 'G' in style: 

994 style = style.copy() 

995 color = style.pop('G') 

996 

997 self._labels.append( 

998 (lon, lat, text, offset_x, offset_y, color, font, font_size, 

999 angle, style)) 

1000 

1001 def add_area_label( 

1002 self, lat, lon, text, 

1003 color=None, 

1004 font='3', 

1005 font_size=None, 

1006 style={}): 

1007 

1008 self._area_labels.append( 

1009 (lon, lat, text, color, font, font_size, style)) 

1010 

1011 def cities_in_region(self): 

1012 from pyrocko.dataset import geonames 

1013 cities = geonames.get_cities_region(region=self.wesn, minpop=0) 

1014 cities.extend(self.custom_cities) 

1015 cities.sort(key=lambda x: x.population) 

1016 return cities 

1017 

1018 def draw_cities(self, 

1019 exact=None, 

1020 include=[], 

1021 exclude=[], 

1022 nmax_soft=10, 

1023 psxy_style=dict(S='s5p', G='black')): 

1024 

1025 cities = self.cities_in_region() 

1026 

1027 if exact is not None: 

1028 cities = [c for c in cities if c.name in exact] 

1029 minpop = None 

1030 else: 

1031 cities = [c for c in cities if c.name not in exclude] 

1032 minpop = 10**3 

1033 for minpop_new in [1e3, 3e3, 1e4, 3e4, 1e5, 3e5, 1e6, 3e6, 1e7]: 

1034 cities_new = [ 

1035 c for c in cities 

1036 if c.population > minpop_new or c.name in include] 

1037 

1038 if len(cities_new) == 0 or ( 

1039 len(cities_new) < 3 and len(cities) < nmax_soft*2): 

1040 break 

1041 

1042 cities = cities_new 

1043 minpop = minpop_new 

1044 if len(cities) <= nmax_soft: 

1045 break 

1046 

1047 if cities: 

1048 lats = [c.lat for c in cities] 

1049 lons = [c.lon for c in cities] 

1050 

1051 self.gmt.psxy( 

1052 in_columns=(lons, lats), 

1053 *self.jxyr, **psxy_style) 

1054 

1055 for c in cities: 

1056 try: 

1057 text = c.name.encode('iso-8859-1').decode('iso-8859-1') 

1058 except UnicodeEncodeError: 

1059 text = c.asciiname 

1060 

1061 self.add_label(c.lat, c.lon, text) 

1062 

1063 self._cities_minpop = minpop 

1064 

1065 def add_stations(self, stations, psxy_style=dict()): 

1066 

1067 default_psxy_style = { 

1068 'S': 't8p', 

1069 'G': 'black' 

1070 } 

1071 default_psxy_style.update(psxy_style) 

1072 

1073 lats, lons = zip(*[s.effective_latlon for s in stations]) 

1074 

1075 self.gmt.psxy( 

1076 in_columns=(lons, lats), 

1077 *self.jxyr, **default_psxy_style) 

1078 

1079 for station in stations: 

1080 self.add_label( 

1081 station.effective_lat, 

1082 station.effective_lon, 

1083 '.'.join(x for x in (station.network, station.station) if x)) 

1084 

1085 def add_kite_scene(self, scene): 

1086 tile = FloatTile( 

1087 scene.frame.llLon, 

1088 scene.frame.llLat, 

1089 scene.frame.dLon, 

1090 scene.frame.dLat, 

1091 scene.displacement) 

1092 

1093 return tile 

1094 

1095 def add_gnss_campaign(self, campaign, psxy_style=None, offset_scale=None, 

1096 labels=True, vertical=False, fontsize=10): 

1097 

1098 stations = campaign.stations 

1099 

1100 if offset_scale is None: 

1101 offset_scale = num.zeros(campaign.nstations) 

1102 for ista, sta in enumerate(stations): 

1103 for comp in sta.components.values(): 

1104 offset_scale[ista] += comp.shift 

1105 offset_scale = num.sqrt(offset_scale**2).max() 

1106 

1107 size = math.sqrt(self.height**2 + self.width**2) 

1108 scale = (size/10.) / offset_scale 

1109 logger.debug('GNSS: Using offset scale %f, map scale %f', 

1110 offset_scale, scale) 

1111 

1112 lats, lons = zip(*[s.effective_latlon for s in stations]) 

1113 

1114 if vertical: 

1115 rows = [[lons[ista], lats[ista], 

1116 0., -s.up.shift, 

1117 (s.east.sigma + s.north.sigma) if s.east.sigma else 0., 

1118 s.up.sigma, 0., 

1119 s.code if labels else None] 

1120 for ista, s in enumerate(stations) 

1121 if s.up is not None] 

1122 

1123 else: 

1124 rows = [[lons[ista], lats[ista], 

1125 -s.east.shift, -s.north.shift, 

1126 s.east.sigma, s.north.sigma, s.correlation_ne, 

1127 s.code if labels else None] 

1128 for ista, s in enumerate(stations) 

1129 if s.east is not None or s.north is not None] 

1130 

1131 default_psxy_style = { 

1132 'h': 0, 

1133 'W': '2p,black', 

1134 'A': '+p2p,black+b+a40', 

1135 'G': 'black', 

1136 'L': True, 

1137 'S': 'e%dc/0.95/%d' % (scale, fontsize), 

1138 } 

1139 

1140 if not labels: 

1141 for row in rows: 

1142 row.pop(-1) 

1143 

1144 if psxy_style is not None: 

1145 default_psxy_style.update(psxy_style) 

1146 

1147 self.gmt.psvelo( 

1148 in_rows=rows, 

1149 *self.jxyr, 

1150 **default_psxy_style) 

1151 

1152 def draw_plates(self): 

1153 from pyrocko.dataset import tectonics 

1154 

1155 neast = 20 

1156 nnorth = max(1, int(round(num.round(self._hreg/self._wreg * neast)))) 

1157 norths = num.linspace(-self._hreg*0.5, self._hreg*0.5, nnorth) 

1158 easts = num.linspace(-self._wreg*0.5, self._wreg*0.5, neast) 

1159 norths2 = num.repeat(norths, neast) 

1160 easts2 = num.tile(easts, nnorth) 

1161 lats, lons = od.ne_to_latlon( 

1162 self.lat, self.lon, norths2, easts2) 

1163 

1164 bird = tectonics.PeterBird2003() 

1165 plates = bird.get_plates() 

1166 

1167 color_plates = gmtpy.color('aluminium5') 

1168 color_velocities = gmtpy.color('skyblue1') 

1169 color_velocities_lab = gmtpy.color(darken(gmtpy.color_tup('skyblue1'))) 

1170 

1171 points = num.vstack((lats, lons)).T 

1172 used = [] 

1173 for plate in plates: 

1174 mask = plate.contains_points(points) 

1175 if num.any(mask): 

1176 used.append((plate, mask)) 

1177 

1178 if len(used) > 1: 

1179 

1180 candi_fixed = {} 

1181 

1182 label_data = [] 

1183 for plate, mask in used: 

1184 

1185 mean_north = num.mean(norths2[mask]) 

1186 mean_east = num.mean(easts2[mask]) 

1187 iorder = num.argsort(num.sqrt( 

1188 (norths2[mask] - mean_north)**2 + 

1189 (easts2[mask] - mean_east)**2)) 

1190 

1191 lat_candis = lats[mask][iorder] 

1192 lon_candis = lons[mask][iorder] 

1193 

1194 candi_fixed[plate.name] = lat_candis.size 

1195 

1196 label_data.append(( 

1197 lat_candis, lon_candis, plate, color_plates)) 

1198 

1199 boundaries = bird.get_boundaries() 

1200 

1201 size = 1. 

1202 

1203 psxy_kwargs = [] 

1204 

1205 for boundary in boundaries: 

1206 if num.any(points_in_region(boundary.points, self._wesn)): 

1207 for typ, part in boundary.split_types( 

1208 [['SUB'], 

1209 ['OSR', 'OTF', 'OCB', 'CTF', 'CCB', 'CRB']]): 

1210 

1211 lats, lons = part.T 

1212 

1213 kwargs = {} 

1214 if typ[0] == 'SUB': 

1215 if boundary.kind == '\\': 

1216 kwargs['S'] = 'f%g/%gp+t+r' % ( 

1217 0.45*size, 3.*size) 

1218 elif boundary.kind == '/': 

1219 kwargs['S'] = 'f%g/%gp+t+l' % ( 

1220 0.45*size, 3.*size) 

1221 

1222 kwargs['G'] = color_plates 

1223 

1224 kwargs['in_columns'] = (lons, lats) 

1225 kwargs['W'] = '%gp,%s' % (size, color_plates), 

1226 

1227 psxy_kwargs.append(kwargs) 

1228 

1229 if boundary.kind == '\\': 

1230 if boundary.plate_name2 in candi_fixed: 

1231 candi_fixed[boundary.plate_name2] += \ 

1232 neast*nnorth 

1233 

1234 elif boundary.kind == '/': 

1235 if boundary.plate_name1 in candi_fixed: 

1236 candi_fixed[boundary.plate_name1] += \ 

1237 neast*nnorth 

1238 

1239 candi_fixed = [name for name in sorted( 

1240 list(candi_fixed.keys()), key=lambda name: -candi_fixed[name])] 

1241 

1242 candi_fixed.append(None) 

1243 

1244 gsrm = tectonics.GSRM1() 

1245 

1246 for name in candi_fixed: 

1247 if name not in gsrm.plate_names() \ 

1248 and name not in gsrm.plate_alt_names(): 

1249 

1250 continue 

1251 

1252 lats, lons, vnorth, veast, vnorth_err, veast_err, corr = \ 

1253 gsrm.get_velocities(name, region=self._wesn) 

1254 

1255 fixed_plate_name = name 

1256 

1257 if self.show_plate_velocities: 

1258 self.gmt.psvelo( 

1259 in_columns=( 

1260 lons, lats, veast, vnorth, veast_err, vnorth_err, 

1261 corr), 

1262 W='0.25p,%s' % color_velocities, 

1263 A='9p+e+g%s' % color_velocities, 

1264 S='e0.2p/0.95/10', 

1265 *self.jxyr) 

1266 

1267 for _ in range(len(lons) // 50 + 1): 

1268 ii = random.randint(0, len(lons)-1) 

1269 v = math.sqrt(vnorth[ii]**2 + veast[ii]**2) 

1270 self.add_label( 

1271 lats[ii], lons[ii], '%.0f' % v, 

1272 font_size=0.7*self.gmt.label_font_size(), 

1273 style=dict( 

1274 G=color_velocities_lab)) 

1275 

1276 break 

1277 

1278 if self.show_plate_names: 

1279 for (lat_candis, lon_candis, plate, color) in label_data: 

1280 full_name = bird.full_name(plate.name) 

1281 if plate.name == fixed_plate_name: 

1282 full_name = '@_' + full_name + '@_' 

1283 

1284 self.add_area_label( 

1285 lat_candis, lon_candis, 

1286 full_name, 

1287 color=color, 

1288 font='3') 

1289 

1290 for kwargs in psxy_kwargs: 

1291 self.gmt.psxy(*self.jxyr, **kwargs) 

1292 

1293 

1294def rand(mi, ma): 

1295 mi = float(mi) 

1296 ma = float(ma) 

1297 return random.random() * (ma-mi) + mi 

1298 

1299 

1300def split_region(region): 

1301 west, east, south, north = topo.positive_region(region) 

1302 if east > 180: 

1303 return [(west, 180., south, north), 

1304 (-180., east-360., south, north)] 

1305 else: 

1306 return [region] 

1307 

1308 

1309class CPTLevel(Object): 

1310 vmin = Float.T() 

1311 vmax = Float.T() 

1312 color_min = Tuple.T(3, Float.T()) 

1313 color_max = Tuple.T(3, Float.T()) 

1314 

1315 

1316class CPT(Object): 

1317 color_below = Tuple.T(3, Float.T(), optional=True) 

1318 color_above = Tuple.T(3, Float.T(), optional=True) 

1319 color_nan = Tuple.T(3, Float.T(), optional=True) 

1320 levels = List.T(CPTLevel.T()) 

1321 

1322 def scale(self, vmin, vmax): 

1323 vmin_old, vmax_old = self.levels[0].vmin, self.levels[-1].vmax 

1324 for level in self.levels: 

1325 level.vmin = (level.vmin - vmin_old) / (vmax_old - vmin_old) * \ 

1326 (vmax - vmin) + vmin 

1327 level.vmax = (level.vmax - vmin_old) / (vmax_old - vmin_old) * \ 

1328 (vmax - vmin) + vmin 

1329 

1330 def discretize(self, nlevels): 

1331 colors = [] 

1332 vals = [] 

1333 for level in self.levels: 

1334 vals.append(level.vmin) 

1335 vals.append(level.vmax) 

1336 colors.append(level.color_min) 

1337 colors.append(level.color_max) 

1338 

1339 r, g, b = num.array(colors, dtype=float).T 

1340 vals = num.array(vals, dtype=float) 

1341 

1342 vmin, vmax = self.levels[0].vmin, self.levels[-1].vmax 

1343 x = num.linspace(vmin, vmax, nlevels+1) 

1344 rd = num.interp(x, vals, r) 

1345 gd = num.interp(x, vals, g) 

1346 bd = num.interp(x, vals, b) 

1347 

1348 levels = [] 

1349 for ilevel in range(nlevels): 

1350 color = ( 

1351 float(0.5*(rd[ilevel]+rd[ilevel+1])), 

1352 float(0.5*(gd[ilevel]+gd[ilevel+1])), 

1353 float(0.5*(bd[ilevel]+bd[ilevel+1]))) 

1354 

1355 levels.append(CPTLevel( 

1356 vmin=x[ilevel], 

1357 vmax=x[ilevel+1], 

1358 color_min=color, 

1359 color_max=color)) 

1360 

1361 cpt = CPT( 

1362 color_below=self.color_below, 

1363 color_above=self.color_above, 

1364 color_nan=self.color_nan, 

1365 levels=levels) 

1366 

1367 return cpt 

1368 

1369 

1370class CPTParseError(Exception): 

1371 pass 

1372 

1373 

1374def read_cpt(filename): 

1375 with open(filename) as f: 

1376 color_below = None 

1377 color_above = None 

1378 color_nan = None 

1379 levels = [] 

1380 try: 

1381 for line in f: 

1382 line = line.strip() 

1383 toks = line.split() 

1384 

1385 if line.startswith('#'): 

1386 continue 

1387 

1388 elif line.startswith('B'): 

1389 color_below = tuple(map(float, toks[1:4])) 

1390 

1391 elif line.startswith('F'): 

1392 color_above = tuple(map(float, toks[1:4])) 

1393 

1394 elif line.startswith('N'): 

1395 color_nan = tuple(map(float, toks[1:4])) 

1396 

1397 else: 

1398 values = list(map(float, line.split())) 

1399 vmin = values[0] 

1400 color_min = tuple(values[1:4]) 

1401 vmax = values[4] 

1402 color_max = tuple(values[5:8]) 

1403 levels.append(CPTLevel( 

1404 vmin=vmin, 

1405 vmax=vmax, 

1406 color_min=color_min, 

1407 color_max=color_max)) 

1408 

1409 except Exception: 

1410 raise CPTParseError() 

1411 

1412 return CPT( 

1413 color_below=color_below, 

1414 color_above=color_above, 

1415 color_nan=color_nan, 

1416 levels=levels) 

1417 

1418 

1419def color_to_int(color): 

1420 return tuple(max(0, min(255, int(round(x)))) for x in color) 

1421 

1422 

1423def write_cpt(cpt, filename): 

1424 with open(filename, 'w') as f: 

1425 for level in cpt.levels: 

1426 f.write( 

1427 '%e %i %i %i %e %i %i %i\n' % 

1428 ((level.vmin, ) + color_to_int(level.color_min) + 

1429 (level.vmax, ) + color_to_int(level.color_max))) 

1430 

1431 if cpt.color_below: 

1432 f.write('B %i %i %i\n' % color_to_int(cpt.color_below)) 

1433 

1434 if cpt.color_above: 

1435 f.write('F %i %i %i\n' % color_to_int(cpt.color_above)) 

1436 

1437 if cpt.color_nan: 

1438 f.write('N %i %i %i\n' % color_to_int(cpt.color_nan)) 

1439 

1440 

1441def cpt_merge_wet_dry(wet, dry): 

1442 levels = [] 

1443 for level in wet.levels: 

1444 if level.vmin < 0.: 

1445 if level.vmax > 0.: 

1446 level.vmax = 0. 

1447 

1448 levels.append(level) 

1449 

1450 for level in dry.levels: 

1451 if level.vmax > 0.: 

1452 if level.vmin < 0.: 

1453 level.vmin = 0. 

1454 

1455 levels.append(level) 

1456 

1457 combi = CPT( 

1458 color_below=wet.color_below, 

1459 color_above=dry.color_above, 

1460 color_nan=dry.color_nan, 

1461 levels=levels) 

1462 

1463 return combi 

1464 

1465 

1466if __name__ == '__main__': 

1467 from pyrocko import util 

1468 util.setup_logging('pyrocko.automap', 'info') 

1469 

1470 import sys 

1471 if len(sys.argv) == 2: 

1472 

1473 n = int(sys.argv[1]) 

1474 

1475 for i in range(n): 

1476 m = Map( 

1477 lat=rand(-60., 60.), 

1478 lon=rand(-180., 180.), 

1479 radius=math.exp(rand(math.log(500*km), math.log(3000*km))), 

1480 width=30., height=30., 

1481 show_grid=True, 

1482 show_topo=True, 

1483 color_dry=(238, 236, 230), 

1484 topo_cpt_wet='light_sea_uniform', 

1485 topo_cpt_dry='light_land_uniform', 

1486 illuminate=True, 

1487 illuminate_factor_ocean=0.15, 

1488 show_rivers=False, 

1489 show_plates=True) 

1490 

1491 m.draw_cities() 

1492 print(m) 

1493 m.save('map_%02i.pdf' % i)