Compare commits
No commits in common. "bbd07b7f3a942a7424b5a18a8a440bd83c9eb1a6" and "902ae26b80cd53d7a2dfe6081f5ca7a62bdc3f17" have entirely different histories.
bbd07b7f3a
...
902ae26b80
4 changed files with 43 additions and 205 deletions
|
@ -4,11 +4,7 @@ import argparse
|
||||||
import cairo
|
import cairo
|
||||||
|
|
||||||
from xmet.db import Database
|
from xmet.db import Database
|
||||||
|
from xmet.spc import SPCOutlookParser, SPCOutlook, SPCOutlookMap
|
||||||
from xmet.spc import SPCOutlookParser, \
|
|
||||||
SPCOutlook, \
|
|
||||||
SPCOutlookMap, \
|
|
||||||
SPCOutlookType
|
|
||||||
|
|
||||||
ASSETS = {
|
ASSETS = {
|
||||||
'light': {
|
'light': {
|
||||||
|
@ -36,7 +32,6 @@ def render_categorical(conus: SPCOutlookMap,
|
||||||
conus.draw_categories(cr, outlook)
|
conus.draw_categories(cr, outlook)
|
||||||
conus.draw_cities(cr, db)
|
conus.draw_cities(cr, db)
|
||||||
conus.draw_logo(cr, assets['logo'])
|
conus.draw_logo(cr, assets['logo'])
|
||||||
conus.draw_legend(cr, SPCOutlookType.CATEGORICAL)
|
|
||||||
|
|
||||||
if args.dark:
|
if args.dark:
|
||||||
cr.set_source_rgb(1, 1, 1)
|
cr.set_source_rgb(1, 1, 1)
|
||||||
|
@ -59,7 +54,6 @@ def render_probabilistic(conus: SPCOutlookMap,
|
||||||
conus.draw_probabilities(cr, outlook, hazard.upper())
|
conus.draw_probabilities(cr, outlook, hazard.upper())
|
||||||
conus.draw_cities(cr, db)
|
conus.draw_cities(cr, db)
|
||||||
conus.draw_logo(cr, assets['logo'])
|
conus.draw_logo(cr, assets['logo'])
|
||||||
conus.draw_legend(cr, SPCOutlookType.PROBABILISTIC)
|
|
||||||
|
|
||||||
if args.dark:
|
if args.dark:
|
||||||
cr.set_source_rgb(1, 1, 1)
|
cr.set_source_rgb(1, 1, 1)
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import math
|
|
||||||
import cairo
|
|
||||||
|
|
||||||
def draw_rounded_rect(cr: cairo.Context,
|
|
||||||
x: float,
|
|
||||||
y: float,
|
|
||||||
width: float,
|
|
||||||
height: float,
|
|
||||||
radius: float):
|
|
||||||
rect_width = width - 2 * radius
|
|
||||||
rect_height = height - 2 * radius
|
|
||||||
|
|
||||||
# Upper right
|
|
||||||
cr.arc(x + radius + rect_width, y + radius, radius,
|
|
||||||
-math.pi / 2, 0)
|
|
||||||
|
|
||||||
# Lower right
|
|
||||||
cr.arc(x + radius + rect_width, y + radius + rect_height, radius,
|
|
||||||
0, math.pi / 2)
|
|
||||||
|
|
||||||
# Lower left
|
|
||||||
cr.arc(x + radius, y + radius + rect_height, radius,
|
|
||||||
math.pi / 2, math.pi)
|
|
||||||
|
|
||||||
# Upper right
|
|
||||||
cr.arc(x + radius, y + radius, radius,
|
|
||||||
math.pi, math.pi * 1.5)
|
|
||||||
|
|
||||||
cr.line_to(x + radius + rect_width, y)
|
|
|
@ -88,9 +88,9 @@ class EquirectMap():
|
||||||
elif city.population >= 500000:
|
elif city.population >= 500000:
|
||||||
radius = 3
|
radius = 3
|
||||||
elif city.population >= 100000:
|
elif city.population >= 100000:
|
||||||
radius = 2.5
|
radius = 2
|
||||||
else:
|
else:
|
||||||
radius = 1.3
|
radius = 1
|
||||||
|
|
||||||
extents = cr.text_extents(city.name)
|
extents = cr.text_extents(city.name)
|
||||||
|
|
||||||
|
|
207
lib/xmet/spc.py
207
lib/xmet/spc.py
|
@ -9,7 +9,6 @@ from xmet.coord import COORD_SYSTEM
|
||||||
from xmet.city import City
|
from xmet.city import City
|
||||||
from xmet.map import EquirectMap, MAP_SCREEN_DIMENSIONS, MAP_BOUNDS
|
from xmet.map import EquirectMap, MAP_SCREEN_DIMENSIONS, MAP_BOUNDS
|
||||||
from xmet.afos import MONTHS, TIMEZONES
|
from xmet.afos import MONTHS, TIMEZONES
|
||||||
from xmet.draw import draw_rounded_rect
|
|
||||||
|
|
||||||
from pyiem.nws.products._outlook_util import (
|
from pyiem.nws.products._outlook_util import (
|
||||||
condition_segment,
|
condition_segment,
|
||||||
|
@ -65,58 +64,57 @@ RE_POINTS_START = re.compile(r'''
|
||||||
RE_POINTS = re.compile(r'^(?:\s+\d{8}){1,6}$')
|
RE_POINTS = re.compile(r'^(?:\s+\d{8}){1,6}$')
|
||||||
|
|
||||||
CITIES = {
|
CITIES = {
|
||||||
|
'WA': ('Seattle', 'Spokane'),
|
||||||
|
'OR': ('Portland', 'Eugene', 'Medford'),
|
||||||
'CA': (
|
'CA': (
|
||||||
'Redding', 'Sacramento', 'San Francisco', 'Fresno', 'Santa Barbara',
|
'Redding', 'Sacramento', 'San Francisco', 'Fresno', 'Santa Barbara',
|
||||||
'Los Angeles', 'San Diego'
|
'Los Angeles', 'San Diego'
|
||||||
),
|
),
|
||||||
|
'ID': ('Boise', 'Pocatello'),
|
||||||
|
'NV': ('Elko', 'Reno', 'Las Vegas'),
|
||||||
|
'UT': ('Salt Lake City', 'Cedar City'),
|
||||||
|
'AZ': ('Flagstaff', 'Phoenix', 'Tucson'),
|
||||||
|
'MT': ('Great Falls', 'Missoula', 'Butte', 'Billings'),
|
||||||
|
'WY': ('Sheridan', 'Jackson', 'Casper', 'Cheyenne'),
|
||||||
|
'CO': ('Denver', 'Grand Junction', 'Pueblo', 'Durango'),
|
||||||
|
'NM': ('Santa Fe', 'Albuquerque', 'Las Cruces'),
|
||||||
|
'ND': ('Minot', 'Bismarck', 'Fargo', 'Grand Forks'),
|
||||||
|
'SD': ('Aberdeen', 'Pierre', 'Rapid City', 'Sioux Falls'),
|
||||||
|
'NE': ('Omaha', 'Lincoln', 'McCook', 'Norfolk'),
|
||||||
|
'KS': ('Wichita', 'Colby', 'Liberal', 'Garden City'),
|
||||||
|
'OK': ('Woodward', 'Tulsa', 'Oklahoma City', 'Norman', 'Altus'),
|
||||||
'TX': (
|
'TX': (
|
||||||
'Amarillo', 'Wichita Falls', 'Lubbock', 'Dallas', 'Abilene',
|
'Amarillo', 'Wichita Falls', 'Lubbock', 'Dallas', 'Abilene',
|
||||||
'Midland', 'Waco', 'Austin', 'San Antonio', 'Houston',
|
'Midland', 'Waco', 'Austin', 'San Antonio', 'Houston',
|
||||||
'Corpus Christi', 'Brownsville'
|
'Corpus Christi', 'Brownsville'
|
||||||
),
|
),
|
||||||
'AL': ('Tuscaloosa', 'Mobile'),
|
|
||||||
'AR': ('Little Rock', 'Bentonville'),
|
|
||||||
'AZ': ('Flagstaff', 'Phoenix', 'Tucson'),
|
|
||||||
'CO': ('Denver', 'Grand Junction', 'Pueblo', 'Durango'),
|
|
||||||
'CT': ('Hartford',),
|
|
||||||
'DC': ('Washington',),
|
|
||||||
'FL': ('Tallahassee', 'Jacksonville', 'Orlando', 'Tampa', 'Miami'),
|
|
||||||
'GA': ('Atlanta', 'Columbus'),
|
|
||||||
'IA': ('Des Moines',),
|
|
||||||
'ID': ('Boise', 'Pocatello'),
|
|
||||||
'IL': ('Chicago', 'Peoria', 'Springfield'),
|
|
||||||
'IN': ('Fort Wayne', 'Indianapolis'),
|
|
||||||
'KS': ('Wichita', 'Colby', 'Liberal', 'Garden City'),
|
|
||||||
'KY': ('Louisville', 'Paducah', 'Bowling Green'),
|
|
||||||
'LA': ('Shreveport', 'New Orleans', 'Alexandria',),
|
'LA': ('Shreveport', 'New Orleans', 'Alexandria',),
|
||||||
'MA': ('Boston',),
|
'AR': ('Little Rock', 'Bentonville'),
|
||||||
'MD': ('Baltimore',),
|
|
||||||
'ME': ('Portland',),
|
|
||||||
'MI': ('Marquette', 'Detroit'),
|
|
||||||
'MN': ('Minneapolis', 'Duluth'),
|
|
||||||
'MO': ('Jefferson City', 'Kansas City'),
|
'MO': ('Jefferson City', 'Kansas City'),
|
||||||
'MS': ('Jackson',),
|
'IA': ('Des Moines',),
|
||||||
'MT': ('Great Falls', 'Missoula', 'Butte', 'Billings'),
|
'MN': ('Minneapolis', 'Duluth'),
|
||||||
'NC': ('Charlotte', 'Raleigh', 'Wilmington'),
|
'MI': ('Marquette', 'Detroit'),
|
||||||
'ND': ('Minot', 'Bismarck', 'Fargo', 'Grand Forks'),
|
|
||||||
'NE': ('Omaha', 'Lincoln', 'McCook', 'Norfolk'),
|
|
||||||
'NM': ('Santa Fe', 'Albuquerque', 'Las Cruces'),
|
|
||||||
'NV': ('Elko', 'Reno', 'Las Vegas'),
|
|
||||||
'NY': ('Rochester', 'Buffalo', 'New York'),
|
|
||||||
'OH': ('Columbus', 'Cincinnati'),
|
|
||||||
'OK': ('Woodward', 'Tulsa', 'Oklahoma City', 'Norman', 'Altus'),
|
|
||||||
'OR': ('Portland', 'Eugene', 'Medford'),
|
|
||||||
'PA': ('Philadelphia', 'Pittsburgh', 'Scranton'),
|
|
||||||
'SC': ('Columbia', 'Charleston'),
|
|
||||||
'SD': ('Aberdeen', 'Pierre', 'Rapid City', 'Sioux Falls'),
|
|
||||||
'TN': ('Nashville', 'Jackson', 'Memphis'),
|
|
||||||
'UT': ('Salt Lake City', 'Cedar City'),
|
|
||||||
'VA': ('Roanoke', 'Richmond'),
|
|
||||||
'VT': ('Burlington',),
|
|
||||||
'WA': ('Seattle', 'Spokane'),
|
|
||||||
'WI': ('Green Bay', 'Milwaukee'),
|
'WI': ('Green Bay', 'Milwaukee'),
|
||||||
|
'IL': ('Chicago', 'Peoria', 'Springfield'),
|
||||||
|
'KY': ('Louisville', 'Paducah', 'Bowling Green'),
|
||||||
|
'TN': ('Nashville', 'Jackson', 'Memphis'),
|
||||||
|
'MS': ('Jackson',),
|
||||||
|
'AL': ('Tuscaloosa', 'Mobile'),
|
||||||
|
'GA': ('Atlanta', 'Columbus'),
|
||||||
|
'FL': ('Tallahassee', 'Jacksonville', 'Orlando', 'Tampa', 'Miami'),
|
||||||
|
'SC': ('Columbia', 'Charleston'),
|
||||||
|
'NC': ('Charlotte', 'Raleigh', 'Wilmington'),
|
||||||
|
'VA': ('Roanoke', 'Richmond'),
|
||||||
'WV': ('Charleston',),
|
'WV': ('Charleston',),
|
||||||
'WY': ('Sheridan', 'Jackson', 'Casper', 'Cheyenne'),
|
'DC': ('Washington',),
|
||||||
|
'MD': ('Baltimore',),
|
||||||
|
'OH': ('Columbus', 'Cincinnati'),
|
||||||
|
'IN': ('Fort Wayne', 'Indianapolis'),
|
||||||
|
'PA': ('Philadelphia', 'Pittsburgh', 'Scranton'),
|
||||||
|
'NY': ('Rochester', 'Buffalo', 'New York'),
|
||||||
|
'VT': ('Burlington',),
|
||||||
|
'ME': ('Portland',),
|
||||||
|
'MA': ('Boston',)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SPCOutlookParserException(Exception):
|
class SPCOutlookParserException(Exception):
|
||||||
|
@ -544,114 +542,13 @@ class SPCOutlookParser():
|
||||||
|
|
||||||
return self.outlook
|
return self.outlook
|
||||||
|
|
||||||
class SPCOutlookType(enum.Enum):
|
|
||||||
CATEGORICAL = 1
|
|
||||||
PROBABILISTIC = 2
|
|
||||||
|
|
||||||
class SPCOutlookLegend():
|
|
||||||
MARGIN = 16
|
|
||||||
RADIUS = 16
|
|
||||||
|
|
||||||
COLOR_WIDTH = 32
|
|
||||||
COLOR_HEIGHT = 32
|
|
||||||
|
|
||||||
FONT_FACE = 'Muli'
|
|
||||||
FONT_SIZE = 16
|
|
||||||
|
|
||||||
def __init__(self, colors: dict[str, tuple[float, float, float]]):
|
|
||||||
self.colors = colors
|
|
||||||
|
|
||||||
self.text_width = 0.0
|
|
||||||
self.width = None
|
|
||||||
self.height = None
|
|
||||||
|
|
||||||
def find_size(self, cr: cairo.Context) -> tuple[float, float]:
|
|
||||||
cr.save()
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
cr.select_font_face(self.FONT_FACE,
|
|
||||||
cairo.FONT_SLANT_NORMAL,
|
|
||||||
cairo.FONT_WEIGHT_BOLD)
|
|
||||||
|
|
||||||
cr.set_font_size(self.FONT_SIZE)
|
|
||||||
|
|
||||||
for key in self.colors:
|
|
||||||
if key is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
extents = cr.text_extents(str(key))
|
|
||||||
|
|
||||||
if self.text_width < extents.width:
|
|
||||||
self.text_width = extents.width
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
self.width = 3 * self.MARGIN + self.COLOR_WIDTH + self.text_width
|
|
||||||
self.height = self.MARGIN + count * (self.COLOR_HEIGHT + self.MARGIN)
|
|
||||||
|
|
||||||
cr.restore()
|
|
||||||
|
|
||||||
return self.width, self.height
|
|
||||||
|
|
||||||
def draw_item(self, cr: cairo.Context, key: str, x: float, y: float):
|
|
||||||
cr.save()
|
|
||||||
|
|
||||||
cr.set_source_rgb(*self.colors[key])
|
|
||||||
cr.rectangle(x, y, self.COLOR_WIDTH, self.COLOR_HEIGHT)
|
|
||||||
cr.fill()
|
|
||||||
|
|
||||||
cr.set_source_rgb(0, 0, 0)
|
|
||||||
cr.rectangle(x, y, self.COLOR_WIDTH, self.COLOR_HEIGHT)
|
|
||||||
cr.stroke()
|
|
||||||
|
|
||||||
text = str(key)
|
|
||||||
|
|
||||||
extents = cr.text_extents(text)
|
|
||||||
|
|
||||||
cr.move_to(x + self.COLOR_WIDTH + self.MARGIN,
|
|
||||||
y + self.COLOR_WIDTH / 2 + extents.height / 2)
|
|
||||||
|
|
||||||
cr.show_text(text)
|
|
||||||
|
|
||||||
cr.restore()
|
|
||||||
|
|
||||||
def draw(self, cr: cairo.Context, x: float, y: float):
|
|
||||||
cr.save()
|
|
||||||
|
|
||||||
cr.select_font_face(self.FONT_FACE,
|
|
||||||
cairo.FONT_SLANT_NORMAL,
|
|
||||||
cairo.FONT_WEIGHT_BOLD)
|
|
||||||
|
|
||||||
cr.set_font_size(self.FONT_SIZE)
|
|
||||||
|
|
||||||
cr.set_source_rgba(0.2, 0.2, 0.2, 0.5)
|
|
||||||
draw_rounded_rect(cr, x, y, self.width, self.height, self.RADIUS)
|
|
||||||
|
|
||||||
cr.fill()
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
for key in self.colors:
|
|
||||||
if key is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
item_y = y + self.MARGIN + i * (self.COLOR_HEIGHT + self.MARGIN)
|
|
||||||
|
|
||||||
self.draw_item(cr, key, x + self.MARGIN, item_y)
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
cr.restore()
|
|
||||||
|
|
||||||
class SPCOutlookMap(EquirectMap):
|
class SPCOutlookMap(EquirectMap):
|
||||||
TEXT_FONT = 'Muli'
|
TEXT_FONT = 'Muli'
|
||||||
|
|
||||||
LOGO_RATIO = 75.0 / 275.0
|
LOGO_RATIO = 75.0 / 275.0
|
||||||
LOGO_WIDTH = 360
|
LOGO_WIDTH = 360
|
||||||
LOGO_HEIGHT = LOGO_RATIO * LOGO_WIDTH
|
LOGO_HEIGHT = LOGO_RATIO * LOGO_WIDTH
|
||||||
|
LOGO_MARGIN = 16
|
||||||
MARGIN = 16
|
|
||||||
|
|
||||||
__category_colors__ = {
|
__category_colors__ = {
|
||||||
'TSTM': (212/255.0, 240/255.0, 213/255.0),
|
'TSTM': (212/255.0, 240/255.0, 213/255.0),
|
||||||
|
@ -684,36 +581,12 @@ class SPCOutlookMap(EquirectMap):
|
||||||
cr.line_to(4, 4)
|
cr.line_to(4, 4)
|
||||||
cr.stroke()
|
cr.stroke()
|
||||||
|
|
||||||
def draw_legend(self,
|
|
||||||
cr: cairo.Context,
|
|
||||||
kind: SPCOutlookType):
|
|
||||||
if kind is SPCOutlookType.CATEGORICAL:
|
|
||||||
colors = self.__category_colors__
|
|
||||||
elif kind is SPCOutlookType.PROBABILISTIC:
|
|
||||||
colors = dict()
|
|
||||||
|
|
||||||
for key in self.__probability_colors__:
|
|
||||||
if key is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
text = "%d%%" % int(key * 100)
|
|
||||||
|
|
||||||
colors[text] = self.__probability_colors__[key]
|
|
||||||
|
|
||||||
legend = SPCOutlookLegend(colors)
|
|
||||||
size = legend.find_size(cr)
|
|
||||||
|
|
||||||
x = self.width - 3 * self.MARGIN - size[0]
|
|
||||||
y = self.height - 6 * self.MARGIN - size[1]
|
|
||||||
|
|
||||||
legend.draw(cr, x, y)
|
|
||||||
|
|
||||||
def draw_logo(self, cr: cairo.Context, path: str):
|
def draw_logo(self, cr: cairo.Context, path: str):
|
||||||
cr.save()
|
cr.save()
|
||||||
|
|
||||||
width = self.LOGO_WIDTH
|
width = self.LOGO_WIDTH
|
||||||
height = width * self.LOGO_RATIO
|
height = width * self.LOGO_RATIO
|
||||||
margin = self.MARGIN
|
margin = self.LOGO_MARGIN
|
||||||
|
|
||||||
x = margin
|
x = margin
|
||||||
y = self.height - height - margin
|
y = self.height - height - margin
|
||||||
|
|
Loading…
Add table
Reference in a new issue