mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 15:33:44 +03:00
render individual points from extra KML, and refactor some circle helpers
This commit is contained in:
parent
f141329e85
commit
f36c94c730
@ -594,3 +594,9 @@ CarParking
|
|||||||
- rename to ParkedCar
|
- rename to ParkedCar
|
||||||
SidewalkSpot
|
SidewalkSpot
|
||||||
- this should cache lane and distance. :)
|
- this should cache lane and distance. :)
|
||||||
|
|
||||||
|
## Notes on King County GIS datasets
|
||||||
|
|
||||||
|
- TODO: https://data-seattlecitygis.opendata.arcgis.com/datasets/channelization
|
||||||
|
|
||||||
|
- https://data-seattlecitygis.opendata.arcgis.com/datasets/street-signs
|
||||||
|
5
docs/references.md
Normal file
5
docs/references.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# References
|
||||||
|
|
||||||
|
## Groups that may be eventually interested
|
||||||
|
|
||||||
|
- Seattle Times Traffic Lab
|
@ -175,9 +175,9 @@
|
|||||||
1.0
|
1.0
|
||||||
],
|
],
|
||||||
"ExtraShape": [
|
"ExtraShape": [
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
1.0,
|
1.0,
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
1.0
|
1.0
|
||||||
],
|
],
|
||||||
"MatchClassification": [
|
"MatchClassification": [
|
||||||
|
@ -282,7 +282,7 @@ impl UI {
|
|||||||
|
|
||||||
let hit = vertical_pl.intersection(&horiz_pl).unwrap();
|
let hit = vertical_pl.intersection(&horiz_pl).unwrap();
|
||||||
if false {
|
if false {
|
||||||
g.draw_ellipse(BLUE, geometry::circle(hit.x(), hit.y(), 1.0));
|
g.draw_ellipse(BLUE, geometry::make_circle(hit, 1.0));
|
||||||
} else {
|
} else {
|
||||||
vertical_pl.trim_to_pt(hit);
|
vertical_pl.trim_to_pt(hit);
|
||||||
horiz_pl.trim_to_pt(hit);
|
horiz_pl.trim_to_pt(hit);
|
||||||
@ -325,7 +325,7 @@ fn draw_polyline(g: &mut GfxCtx, pl: &PolyLine, thickness: f64, color: Color) {
|
|||||||
}
|
}
|
||||||
let radius = 0.5;
|
let radius = 0.5;
|
||||||
for pt in pts {
|
for pt in pts {
|
||||||
g.draw_ellipse(BLUE, geometry::circle(pt.x(), pt.y(), radius));
|
g.draw_ellipse(BLUE, geometry::make_circle(*pt, radius));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use geom::{Bounds, LonLat, PolyLine, Pt2D};
|
use geom::{Bounds, LonLat, PolyLine, Pt2D};
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
use quick_xml::reader::Reader;
|
use quick_xml::reader::Reader;
|
||||||
use std::collections::HashMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::{f64, fmt, io};
|
use std::{f64, fmt, io};
|
||||||
|
|
||||||
@ -17,8 +17,14 @@ impl fmt::Display for ExtraShapeID {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExtraShape {
|
pub struct ExtraShape {
|
||||||
pub id: ExtraShapeID,
|
pub id: ExtraShapeID,
|
||||||
pub pts: PolyLine,
|
pub geom: ExtraShapeGeom,
|
||||||
pub attributes: HashMap<String, String>,
|
pub attributes: BTreeMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ExtraShapeGeom {
|
||||||
|
Point(Pt2D),
|
||||||
|
Points(PolyLine),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::Error> {
|
pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::Error> {
|
||||||
@ -33,7 +39,7 @@ pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::E
|
|||||||
// TODO uncomfortably stateful
|
// TODO uncomfortably stateful
|
||||||
let mut shapes = Vec::new();
|
let mut shapes = Vec::new();
|
||||||
let mut scanned_schema = false;
|
let mut scanned_schema = false;
|
||||||
let mut attributes: HashMap<String, String> = HashMap::new();
|
let mut attributes: BTreeMap<String, String> = BTreeMap::new();
|
||||||
let mut attrib_key: Option<String> = None;
|
let mut attrib_key: Option<String> = None;
|
||||||
|
|
||||||
let mut skipped_count = 0;
|
let mut skipped_count = 0;
|
||||||
@ -75,11 +81,15 @@ pub fn load(path: &String, gps_bounds: &Bounds) -> Result<Vec<ExtraShape>, io::E
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ok {
|
if ok && is_interesting_sign(&attributes) {
|
||||||
let id = ExtraShapeID(shapes.len());
|
let id = ExtraShapeID(shapes.len());
|
||||||
shapes.push(ExtraShape {
|
shapes.push(ExtraShape {
|
||||||
id,
|
id,
|
||||||
pts: PolyLine::new(pts),
|
geom: if pts.len() == 1 {
|
||||||
|
ExtraShapeGeom::Point(pts[0])
|
||||||
|
} else {
|
||||||
|
ExtraShapeGeom::Points(PolyLine::new(pts))
|
||||||
|
},
|
||||||
attributes: attributes.clone(),
|
attributes: attributes.clone(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -126,3 +136,8 @@ fn parse_pt(input: &str, gps_bounds: &Bounds) -> Option<Pt2D> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO only for Street_Signs.kml; this is temporary to explore stuff
|
||||||
|
fn is_interesting_sign(attributes: &BTreeMap<String, String>) -> bool {
|
||||||
|
attributes.get("CATEGORY") == Some(&"REGMIS".to_string())
|
||||||
|
}
|
||||||
|
@ -2,42 +2,70 @@ use aabb_quadtree::geom::Rect;
|
|||||||
use ezgui::GfxCtx;
|
use ezgui::GfxCtx;
|
||||||
use geom::{Polygon, Pt2D};
|
use geom::{Polygon, Pt2D};
|
||||||
use graphics::types::Color;
|
use graphics::types::Color;
|
||||||
use kml::{ExtraShape, ExtraShapeID};
|
use kml::{ExtraShape, ExtraShapeGeom, ExtraShapeID};
|
||||||
use render::{get_bbox, EXTRA_SHAPE_THICKNESS};
|
use map_model::geometry;
|
||||||
use std::collections::HashMap;
|
use render::{get_bbox, EXTRA_SHAPE_POINT_RADIUS, EXTRA_SHAPE_THICKNESS};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Shape {
|
||||||
|
Polygon(Polygon),
|
||||||
|
Circle([f64; 4]),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DrawExtraShape {
|
pub struct DrawExtraShape {
|
||||||
pub id: ExtraShapeID,
|
pub id: ExtraShapeID,
|
||||||
polygon: Polygon,
|
shape: Shape,
|
||||||
attributes: HashMap<String, String>,
|
attributes: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawExtraShape {
|
impl DrawExtraShape {
|
||||||
pub fn new(s: ExtraShape) -> DrawExtraShape {
|
pub fn new(s: ExtraShape) -> DrawExtraShape {
|
||||||
DrawExtraShape {
|
DrawExtraShape {
|
||||||
id: s.id,
|
id: s.id,
|
||||||
polygon: s.pts.make_polygons(EXTRA_SHAPE_THICKNESS).unwrap(),
|
shape: match s.geom {
|
||||||
|
ExtraShapeGeom::Point(pt) => {
|
||||||
|
Shape::Circle(geometry::make_circle(pt, EXTRA_SHAPE_POINT_RADIUS))
|
||||||
|
}
|
||||||
|
ExtraShapeGeom::Points(pl) => {
|
||||||
|
Shape::Polygon(pl.make_polygons(EXTRA_SHAPE_THICKNESS).unwrap())
|
||||||
|
}
|
||||||
|
},
|
||||||
attributes: s.attributes,
|
attributes: s.attributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self, g: &mut GfxCtx, color: Color) {
|
pub fn draw(&self, g: &mut GfxCtx, color: Color) {
|
||||||
g.draw_polygon(color, &self.polygon);
|
match self.shape {
|
||||||
|
Shape::Polygon(ref p) => g.draw_polygon(color, &p),
|
||||||
|
Shape::Circle(c) => g.draw_ellipse(color, c),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
||||||
self.polygon.contains_pt(pt)
|
match self.shape {
|
||||||
|
Shape::Polygon(ref p) => p.contains_pt(pt),
|
||||||
|
Shape::Circle(c) => geometry::point_in_circle(&c, pt),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bbox(&self) -> Rect {
|
pub fn get_bbox(&self) -> Rect {
|
||||||
get_bbox(&self.polygon.get_bounds())
|
match self.shape {
|
||||||
|
Shape::Polygon(ref p) => get_bbox(&p.get_bounds()),
|
||||||
|
Shape::Circle(c) => geometry::circle_to_bbox(&c),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tooltip_lines(&self) -> Vec<String> {
|
pub fn tooltip_lines(&self) -> Vec<String> {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
for (k, v) in &self.attributes {
|
for (k, v) in &self.attributes {
|
||||||
lines.push(format!("{} = {}", k, v));
|
// Make interesting atributes easier to spot
|
||||||
|
if k == "TEXT" {
|
||||||
|
lines.push(format!("*** {} = {}", k, v));
|
||||||
|
} else {
|
||||||
|
lines.push(format!("{} = {}", k, v));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
@ -111,17 +111,17 @@ impl DrawIntersection {
|
|||||||
|
|
||||||
g.draw_ellipse(
|
g.draw_ellipse(
|
||||||
cs.get(Colors::TrafficSignalYellow),
|
cs.get(Colors::TrafficSignalYellow),
|
||||||
geometry::circle(self.center.x(), self.center.y(), radius),
|
geometry::make_circle(self.center, radius),
|
||||||
);
|
);
|
||||||
|
|
||||||
g.draw_ellipse(
|
g.draw_ellipse(
|
||||||
cs.get(Colors::TrafficSignalGreen),
|
cs.get(Colors::TrafficSignalGreen),
|
||||||
geometry::circle(self.center.x(), self.center.y() + (radius * 2.0), radius),
|
geometry::make_circle(self.center.offset(0.0, radius * 2.0), radius),
|
||||||
);
|
);
|
||||||
|
|
||||||
g.draw_ellipse(
|
g.draw_ellipse(
|
||||||
cs.get(Colors::TrafficSignalRed),
|
cs.get(Colors::TrafficSignalRed),
|
||||||
geometry::circle(self.center.x(), self.center.y() - (radius * 2.0), radius),
|
geometry::make_circle(self.center.offset(0.0, radius * -2.0), radius),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,8 @@ impl DrawLane {
|
|||||||
for pair in l.lane_center_pts.points().windows(2) {
|
for pair in l.lane_center_pts.points().windows(2) {
|
||||||
let (pt1, pt2) = (pair[0], pair[1]);
|
let (pt1, pt2) = (pair[0], pair[1]);
|
||||||
g.draw_line(&line, [pt1.x(), pt1.y(), pt2.x(), pt2.y()]);
|
g.draw_line(&line, [pt1.x(), pt1.y(), pt2.x(), pt2.y()]);
|
||||||
g.draw_ellipse(circle_color, geometry::circle(pt1.x(), pt1.y(), 0.4));
|
g.draw_ellipse(circle_color, geometry::make_circle(pt1, 0.4));
|
||||||
g.draw_ellipse(circle_color, geometry::circle(pt2.x(), pt2.y(), 0.8));
|
g.draw_ellipse(circle_color, geometry::make_circle(pt2, 0.8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ use std::f64;
|
|||||||
const PARCEL_BOUNDARY_THICKNESS: f64 = 0.5;
|
const PARCEL_BOUNDARY_THICKNESS: f64 = 0.5;
|
||||||
const BUILDING_BOUNDARY_THICKNESS: f64 = 1.5;
|
const BUILDING_BOUNDARY_THICKNESS: f64 = 1.5;
|
||||||
const EXTRA_SHAPE_THICKNESS: f64 = 1.0;
|
const EXTRA_SHAPE_THICKNESS: f64 = 1.0;
|
||||||
|
const EXTRA_SHAPE_POINT_RADIUS: f64 = 1.0;
|
||||||
|
|
||||||
const TURN_ICON_ARROW_THICKNESS: f64 = geometry::BIG_ARROW_THICKNESS / 3.0;
|
const TURN_ICON_ARROW_THICKNESS: f64 = geometry::BIG_ARROW_THICKNESS / 3.0;
|
||||||
const BIG_ARROW_TIP_LENGTH: f64 = 1.0;
|
const BIG_ARROW_TIP_LENGTH: f64 = 1.0;
|
||||||
|
@ -45,11 +45,7 @@ impl DrawTurn {
|
|||||||
.project_away(TURN_ICON_ARROW_LENGTH / 2.0, angle)
|
.project_away(TURN_ICON_ARROW_LENGTH / 2.0, angle)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let icon_circle = geometry::circle(
|
let icon_circle = geometry::make_circle(icon_center, TURN_ICON_ARROW_LENGTH / 2.0);
|
||||||
icon_center.x(),
|
|
||||||
icon_center.y(),
|
|
||||||
TURN_ICON_ARROW_LENGTH / 2.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
let icon_arrow = [icon_src[0], icon_src[1], icon_dst[0], icon_dst[1]];
|
let icon_arrow = [icon_src[0], icon_src[1], icon_dst[0], icon_dst[1]];
|
||||||
|
|
||||||
@ -87,12 +83,6 @@ impl DrawTurn {
|
|||||||
|
|
||||||
// for the icon
|
// for the icon
|
||||||
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
||||||
let radius = self.icon_circle[2] / 2.0;
|
geometry::point_in_circle(&self.icon_circle, pt)
|
||||||
geometry::point_in_circle(
|
|
||||||
pt.x(),
|
|
||||||
pt.y(),
|
|
||||||
[self.icon_circle[0] + radius, self.icon_circle[1] + radius],
|
|
||||||
radius,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,16 @@ impl UI {
|
|||||||
|
|
||||||
let screen_bbox = self.canvas.get_screen_bbox();
|
let screen_bbox = self.canvas.get_screen_bbox();
|
||||||
|
|
||||||
|
if self.show_extra_shapes.is_enabled() {
|
||||||
|
for s in &self.draw_map
|
||||||
|
.get_extra_shapes_onscreen(screen_bbox, &self.hider)
|
||||||
|
{
|
||||||
|
if s.contains_pt(pt) {
|
||||||
|
return Some(ID::ExtraShape(s.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let lanes_onscreen = if self.show_lanes.is_enabled() {
|
let lanes_onscreen = if self.show_lanes.is_enabled() {
|
||||||
self.draw_map.get_loads_onscreen(screen_bbox, &self.hider)
|
self.draw_map.get_loads_onscreen(screen_bbox, &self.hider)
|
||||||
} else {
|
} else {
|
||||||
@ -230,16 +240,6 @@ impl UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.show_extra_shapes.is_enabled() {
|
|
||||||
for s in &self.draw_map
|
|
||||||
.get_extra_shapes_onscreen(screen_bbox, &self.hider)
|
|
||||||
{
|
|
||||||
if s.contains_pt(pt) {
|
|
||||||
return Some(ID::ExtraShape(s.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_lanes.is_enabled() {
|
if self.show_lanes.is_enabled() {
|
||||||
for l in &lanes_onscreen {
|
for l in &lanes_onscreen {
|
||||||
if l.contains_pt(pt) {
|
if l.contains_pt(pt) {
|
||||||
|
@ -73,6 +73,10 @@ impl Pt2D {
|
|||||||
// DON'T invert y here
|
// DON'T invert y here
|
||||||
Angle::new((to.y() - self.y()).atan2(to.x() - self.x()))
|
Angle::new((to.y() - self.y()).atan2(to.x() - self.x()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn offset(&self, dx: f64, dy: f64) -> Pt2D {
|
||||||
|
Pt2D::new(self.x() + dx, self.y() + dy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Pt2D {
|
impl fmt::Display for Pt2D {
|
||||||
|
@ -10,7 +10,6 @@ dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa
|
|||||||
geo = "0.9.1"
|
geo = "0.9.1"
|
||||||
geom = { path = "../geom" }
|
geom = { path = "../geom" }
|
||||||
ordered-float = "0.5.0"
|
ordered-float = "0.5.0"
|
||||||
piston2d-graphics = "*"
|
|
||||||
pretty_assertions = "0.5.1"
|
pretty_assertions = "0.5.1"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use aabb_quadtree::geom::{Point, Rect};
|
use aabb_quadtree::geom::{Point, Rect};
|
||||||
use geom::{Angle, PolyLine, Polygon, Pt2D};
|
use geom::{Angle, PolyLine, Polygon, Pt2D};
|
||||||
use graphics::math::Vec2d;
|
|
||||||
use std::f64;
|
use std::f64;
|
||||||
|
|
||||||
pub const LANE_THICKNESS: f64 = 2.5;
|
pub const LANE_THICKNESS: f64 = 2.5;
|
||||||
@ -14,15 +13,22 @@ pub fn thick_line_from_angle(thickness: f64, line_length: f64, pt: Pt2D, angle:
|
|||||||
PolyLine::new(vec![pt, pt2]).make_polygons_blindly(thickness)
|
PolyLine::new(vec![pt, pt2]).make_polygons_blindly(thickness)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn point_in_circle(x: f64, y: f64, center: Vec2d, radius: f64) -> bool {
|
pub fn center_of_circle(c: &[f64; 4]) -> Pt2D {
|
||||||
// avoid sqrt by squaring radius instead
|
let radius = c[2] / 2.0;
|
||||||
(x - center[0]).powi(2) + (y - center[1]).powi(2) < radius.powi(2)
|
Pt2D::new(c[0] + radius, c[1] + radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle(center_x: f64, center_y: f64, radius: f64) -> [f64; 4] {
|
pub fn point_in_circle(c: &[f64; 4], pt: Pt2D) -> bool {
|
||||||
|
let radius = c[2] / 2.0;
|
||||||
|
let center = center_of_circle(c);
|
||||||
|
// avoid sqrt by squaring radius instead
|
||||||
|
(pt.x() - center.x()).powi(2) + (pt.y() - center.y()).powi(2) < radius.powi(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_circle(center: Pt2D, radius: f64) -> [f64; 4] {
|
||||||
[
|
[
|
||||||
center_x - radius,
|
center.x() - radius,
|
||||||
center_y - radius,
|
center.y() - radius,
|
||||||
2.0 * radius,
|
2.0 * radius,
|
||||||
2.0 * radius,
|
2.0 * radius,
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,6 @@ extern crate abstutil;
|
|||||||
extern crate dimensioned;
|
extern crate dimensioned;
|
||||||
extern crate geo;
|
extern crate geo;
|
||||||
extern crate geom;
|
extern crate geom;
|
||||||
extern crate graphics;
|
|
||||||
extern crate ordered_float;
|
extern crate ordered_float;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
extern crate pretty_assertions;
|
||||||
|
@ -26,7 +26,7 @@ impl DrawPedestrian {
|
|||||||
|
|
||||||
DrawPedestrian {
|
DrawPedestrian {
|
||||||
id,
|
id,
|
||||||
circle: geometry::circle(pos.x(), pos.y(), RADIUS),
|
circle: geometry::make_circle(pos, RADIUS),
|
||||||
turn_arrow,
|
turn_arrow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,16 +45,10 @@ impl DrawPedestrian {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
pub fn contains_pt(&self, pt: Pt2D) -> bool {
|
||||||
geometry::point_in_circle(
|
geometry::point_in_circle(&self.circle, pt)
|
||||||
pt.x(),
|
|
||||||
pt.y(),
|
|
||||||
[self.circle[0] + RADIUS, self.circle[1] + RADIUS],
|
|
||||||
RADIUS,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus_pt(&self) -> Pt2D {
|
pub fn focus_pt(&self) -> Pt2D {
|
||||||
let radius = self.circle[2] / 2.0;
|
geometry::center_of_circle(&self.circle)
|
||||||
Pt2D::new(self.circle[0] + radius, self.circle[1] + radius)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user