How to find x,y coordinates of a circle in Pygame - math

I am currently trying to create a digital speedometer. The project has come to a screeching halt due to the problem encountered when trying to control the dial. My variable radial_pos is intended to calculate the exact x,y coordinates of each tick of the speedometer and if the user presses K_UP, radial_pos will increase by 1 to show the next tick of the speedometer. However, I'm afraid its been too many years since I've learned the concept of finding the coordinates of a circle, let alone understand it enough to be able to increment it accordingly. Here is my code so far:
import pygame, sys
from pygame.locals import *
pygame.init()
#Screen Resolution
width = 1080
height = 720
#Instrument Characteristics
color = (255,255,255)
background = (0,0,0)
radius_speedometer = 100
weight_speedometer = radius_speedometer-5
#Window Measurements
screen= pygame.display.set_mode((width,height),0,32)
pos_center_x = width/2
pos_center_y = height/2
#Speedometer Dial Position
pos_speedometer_dial_x = pos_center_x - 60
pos_speedometer_dial_y = pos_center_y +60
#Radial Position (hint: ticks on the clock)
'''
radial_pos = ...
'''
while True:
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
sys.exit()
if event.type==KEYDOWN:
if event.key==K_DOWN:
radial_pos=-1
if event.key==K_UP:
radial_pos=+1
if event.type==KEYUP:
if event.key==K_DOWN:
radial_pos=0
if event.key==K_UP:
radial_pos=0
screen.lock()
#Speedometer Ring
pygame.draw.circle(screen, color, (pos_center_x,pos_center_y), radius_speedometer)
pygame.draw.circle(screen, background, (pos_center_x,pos_center_y), weight_speedometer)
#Speedometer Dial
pygame.draw.line(screen, color, (pos_center_x,pos_center_y), (pos_speedometer_dial_x, pos_speedometer_dial_y),5)
screen.unlock()
pygame.display.update()
If anyone has any advice for this particular issue, it would greatly appreciated. Thank you for your time.

It seems I have finally found a solution that works well. Here is the most updated code:
import pygame, sys
from pygame.locals import *
from math import *
pygame.init()
#Screen Resolution
width = 1080
height = 720
#Instrument Characteristics
color = (255,255,255)
background = (0,0,0)
radius_speedometer = 100
weight_speedometer = radius_speedometer-5
#Window Measurements
screen= pygame.display.set_mode((width,height),0,32)
pos_center_x = width/2
pos_center_y = height/2
#Speedometer Dial Position
pos_speedometer_dial_x = pos_center_x - 60
pos_speedometer_dial_y = pos_center_y +60
#Radial Position (hint: ticks on the clock)
radial_pos = 70
move_rad = 0
while True:
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
sys.exit()
if event.type==KEYDOWN:
if event.key==K_DOWN:
move_rad=-0.01
if event.key==K_UP:
move_rad=+0.01
if event.type==KEYUP:
if event.key==K_DOWN:
move_rad=-0.05
if event.key==K_UP:
move_rad=-0.005
angle = 2.0*pi*radial_pos/120.0
radial_pos+=move_rad
pos_speedometer_dial_x = pos_center_x + (radius_speedometer-10)*sin(angle)
pos_speedometer_dial_y = pos_center_y - (radius_speedometer-10)*cos(angle)
screen.lock()
#Speedometer Ring
pygame.draw.circle(screen, color, (pos_center_x,pos_center_y), radius_speedometer)
pygame.draw.circle(screen, background, (pos_center_x,pos_center_y), weight_speedometer)
#Speedometer Dial
pygame.draw.line(screen, color, (pos_center_x,pos_center_y), (pos_speedometer_dial_x, pos_speedometer_dial_y),5)
screen.unlock()
pygame.display.update()
It's not perfect (such as no zero yet), however we are now much closer to completing the first instrument of the dash cluster!

Related

How to calculate lat and lon of a rectangle from a center point

I would like to draw a rectangle based on a center point lat and lon assuming a given length and width, let's say 4.5m and 1.5m, respectively. I guess, we need the bearing too. I've made a simulation by drawing a rectangle on Google Earth, getting the positions and putting them on my code. However, I need something automatic. My question is how can I link the Cartesian coordinates to those four points (rectangle) in meters.
Here is my code:
import geopandas as gpd
from shapely.geometry import Polygon
lat_point_list = [41.404928, 41.404936, 41.404951, 41.404943]
lon_point_list = [2.177339, 2.177331, 2.177353, 2.177365]
polygon_geom = Polygon(zip(lon_point_list, lat_point_list))
import folium
m = folium.Map([41.4049364, 2.1773560], zoom_start=20)
folium.GeoJson(polygon_geom).add_to(m)
folium.LatLngPopup().add_to(m)
m
I would like this:
Update:
I know this is basic trigonometry. If I split the rectsngle into triangles, we can find the different points. I know it is basic for simple exercises, however, I don't know of it changes when using Cartesian coordinates. Then, my goal is to get the points A, B, C and D, knowing the center of the rectangle in latitude and longitude, length and width.
Get the rectangular (NE, SW) bounds of your point and use that as bounds to folium.Rectangle.
Example, using your data. 4.5m and 1.5m are a bit small to see the rectangle:
import geopy
import geopy.distance
import math
import folium
def get_rectangle_bounds(coordinates, width, length):
start = geopy.Point(coordinates)
hypotenuse = math.hypot(width/1000, length/1000)
# Edit used wrong formula to convert radians to degrees, use math builtin function
northeast_angle = 0 - math.degrees(math.atan(width/length))
southwest_angle = 180 - math.degrees(math.atan(width/length))
d = geopy.distance.distance(kilometers=hypotenuse/2)
northeast = d.destination(point=start, bearing=northeast_angle)
southwest = d.destination(point=start, bearing=southwest_angle)
bounds = []
for point in [northeast, southwest]:
coords = (point.latitude, point.longitude)
bounds.append(coords)
return bounds
# To get a rotated rectangle at a bearing, you need to get the points of the the recatangle at that bearing
def get_rotated_points(coordinates, bearing, width, length):
start = geopy.Point(coordinates)
width = width/1000
length = length/1000
rectlength = geopy.distance.distance(kilometers=length)
rectwidth = geopy.distance.distance(kilometers=width)
halfwidth = geopy.distance.distance(kilometers=width/2)
halflength = geopy.distance.distance(kilometers=length/2)
pointAB = halflength.destination(point=start, bearing=bearing)
pointA = halfwidth.destination(point=pointAB, bearing=0-bearing)
pointB = rectwidth.destination(point=pointA, bearing=180-bearing)
pointC = rectlength.destination(point=pointB, bearing=bearing-180)
pointD = rectwidth.destination(point=pointC, bearing=0-bearing)
points = []
for point in [pointA, pointB, pointC, pointD]:
coords = (point.latitude, point.longitude)
points.append(coords)
return points
start_coords = [41.4049364, 2.1773560]
length = 4.50 #in meters
width = 1.50
bearing = 45 #degrees
m = folium.Map(start_coords, zoom_start=20)
bounds = get_rectangle_bounds(tuple(start_coords),width, length )
points = get_rotated_points(tuple(start_coords), bearing, width, length)
folium.Rectangle(bounds=bounds,
fill=True,
color='orange',
tooltip='this is Rectangle'
).add_to(m)
# To draw a rotated rectangle, use folium.Polygon
folium.Polygon(points).add_to(m)

bokeh selected.on_change not working for my current setup

Basically, this is an interactive heatmap but the twist is that the source is updated by reading values from a file that gets updated regularly.
dont bother about the class "generator", it is just for keeping data and it runs regularly threaded
make sure a file named "Server_dump.txt" exists in the same directory of the script with a single number greater than 0 inside before u execute the bokeh script.
what basically happens is i change a number inside the file named "Server_dump.txt" by using echo 4 > Server_dump.txt on bash,
u can put any number other than 4 and the script automatically checks the file and plots the new point.
if u don't use bash, u could use a text editor , replace the number and save, and all will be the same.
the run function inside the generator class is the one which checks if this file was modified , reads the number, transforms it into x& y coords and increments the number of taps associated with these coords and gives the source x,y,taps values based on that number.
well that function works fine and each time i echo a number , the correct rectangle is plotted but,
now I want to add the functionality of that clicking on a certain rectangle triggers a callback to plot a second graph based on the coords of the clicked rectangle but i can't even get it to trigger even though i have tried other examples with selected.on_change in them and they worked fine.
*if i increase self.taps for a certain rect by writing the number to the file multiple times, color gets updated but if i hover over the rect it shows me the past values and not the latest value only .
my bokeh version is 1.0.4
from functools import partial
from random import random,randint
import threading
import time
from tornado import gen
from os.path import getmtime
from math import pi
import pandas as pd
from random import randint, random
from bokeh.io import show
from bokeh.models import LinearColorMapper, BasicTicker, widgets, PrintfTickFormatter, ColorBar, ColumnDataSource, FactorRange
from bokeh.plotting import figure, curdoc
from bokeh.layouts import row, column, gridplot
source = ColumnDataSource(data=dict(x=[], y=[], taps=[]))
doc = curdoc()
#sloppy data receiving function to change data to a plottable shape
class generator(threading.Thread):
def __init__(self):
super(generator, self).__init__()
self.chart_coords = {'x':[],'y':[],'taps':[]}
self.Pi_coords = {}
self.coord = 0
self.pos = 0
self.col = 0
self.row = 0
self.s = 0
self.t = 0
def chart_dict_gen(self,row, col):
self.col = col
self.row = row+1
self.chart_coords['x'] = [i for i in range(1,cla.row)]
self.chart_coords['y'] = [i for i in range(cla.col, 0, -1)] #reversed list because chart requires that
self.chart_coords['taps']= [0]*(row * col)
self.taps = [[0 for y in range(col)] for x in range(row)]
def Pi_dict_gen(self,row,col):
key = 1
for x in range(1,row):
for y in range(1,col):
self.Pi_coords[key] = (x,y)
key = key + 1
def Pi_to_chart(self,N):
x,y = self.Pi_coords[N][0], self.Pi_coords[N][1]
return x,y
def run(self):
while True:
if(self.t == 0):
self.t=1
continue
time.sleep(0.1)
h = getmtime("Server_dump.txt")
if self.s != h:
self.s = h
with open('Server_dump.txt') as f:
m = next(f)
y,x = self.Pi_to_chart(int(m))
self.taps[x][y] += 1
# but update the document from callback
doc.add_next_tick_callback(partial(update, x=x, y=y, taps=self.taps[x][y]))
cla = generator()
cla.chart_dict_gen(15,15)
cla.Pi_dict_gen(15, 15)
x = cla.chart_coords['x']
y = cla.chart_coords['y']
taps = cla.chart_coords['taps']
#gen.coroutine
def update(x, y, taps):
taps += taps
print(x,y,taps)
source.stream(dict(x=[x], y=[y], taps=[taps]))
colors = ["#CCEBFF","#B2E0FF","#99D6FF","#80CCFF","#66c2FF","#4DB8FF","#33ADFF","#19A3FF", "#0099FF", "#008AE6", "#007ACC","#006BB2", "#005C99", "#004C80", "#003D66", "#002E4C", "#001F33", "#000F1A", "#000000"]
mapper = LinearColorMapper(palette=colors, low= 0, high= 15) #low = min(cla.chart_coords['taps']) high = max(cla.chart_coords['taps'])
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"
p = figure(title="Tou",
x_range=list(map(str,x)),
y_range=list(map(str,reversed(y))),
x_axis_location="above",
plot_width=900, plot_height=400,
tools=TOOLS, toolbar_location='below',
tooltips=[('coords', '#y #x'), ('taps', '#taps%')])
p.grid.grid_line_color = "#ffffff"
p.axis.axis_line_color = "#ef4723"
p.axis.major_tick_line_color = "#af0a36"
p.axis.major_label_text_font_size = "7pt"
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.rect(x="x", y="y",
width=0.9, height=0.9,
source=source,
fill_color={'field': 'taps', 'transform': mapper},
line_color = "#ffffff",
)
color_bar = ColorBar(color_mapper=mapper,
major_label_text_font_size="7pt",
ticker=BasicTicker(desired_num_ticks=len(colors)),
formatter=PrintfTickFormatter(format="%d%%"),
label_standoff=6, border_line_color=None, location=(0, 0))
curdoc().theme = 'dark_minimal'
def ck(attr, old, new):
print('here') #doesn't even print hi in the terminal if i click anywhere
source.selected.on_change('indices', ck)
p.add_layout(color_bar, 'right')
doc.add_root(p)
thread = cla
thread.start()
i wanted even to get a printed hi in the terminal but nothing
You have not actually added any selection tool at all to your plot, so no selection is ever made. You have specified:
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"
Those are the only tools that will be added, and none of them make selections, there for nothing will cause source.selection.indices to ever be updated. If you are looking for selections based on tap, you must add a TapTool, e.g. with
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom,tap"
Note that there will not be repeated callbacks if you tap the same rect multiple times. The callback only fires when the selection changes and clicking the same glyph twice in a row results in an identical selection.

ImageView.setImage axes parameter does not switch X-Y dimensions

I have modified the ImageView example by adding the statement data[:, ::10, :] = 0, which sets every tenth element of the middle dimension to 0. The program now shows horizontal lines. This is consistent with the documentation of the ImageView.setImage function: the default axes dictionary is {'t':0, 'x':1, 'y':2, 'c':3}. However, when I change this to {'t':0, 'x':2, 'y':1, 'c':3}, nothing changes where I would expect to get vertical rows.
So my question is: how can I give the row dimension a higher precedence in PyQtGraph? Of course I can transpose all my arrays myself before passing them to the setImage function but I prefer not to. Especially since both Numpy and Qt use the row/column convention and not X before Y. I don't see why PyQtGraph chooses the latter.
For completeness, find my modified ImageView example below.
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
app = QtGui.QApplication([])
## Create window with ImageView widget
win = QtGui.QMainWindow()
win.resize(800,800)
imv = pg.ImageView()
win.setCentralWidget(imv)
win.show()
win.setWindowTitle('pyqtgraph example: ImageView')
## Create random 3D data set with noisy signals
img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
img = img[np.newaxis,:,:]
decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
data = np.random.normal(size=(100, 200, 200))
data += img * decay
data += 2
## Add time-varying signal
sig = np.zeros(data.shape[0])
sig[30:] += np.exp(-np.linspace(1,10, 70))
sig[40:] += np.exp(-np.linspace(1,10, 60))
sig[70:] += np.exp(-np.linspace(1,10, 30))
sig = sig[:,np.newaxis,np.newaxis] * 3
data[:,50:60,50:60] += sig
data[:, ::10, :] = 0 # Make image a-symmetrical
## Display the data and assign each frame a time value from 1.0 to 3.0
imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]),
axes={'t':0, 'x':2, 'y':1, 'c':3}) # doesn't help
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Looking through ImageView.py, setImage() parses the axes dictionary and based on presence of 't' it builds the z-axis/frame slider, and that's it. Rearranging the axes seems unimplemented yet.

How can I show a bitmap in wxGrid header cell?

Using wxPython I want to render a bitmap only in the upper left grid corner cell of a wxGrid, but have no idea how to do this.
I get the Window-Object of the left upper grid corner cell with
mywindow = self.someGrid.GetGridCornerLabelWindow()
But now I cannot set a bitmap to these Window-Object. Can anybody help me?
You will need to create a GridLabelRenderer. There is an example in the wxPython demo that has the following piece of code:
class MyCornerLabelRenderer(glr.GridLabelRenderer):
def __init__(self):
import images
self._bmp = images.Smiles.getBitmap()
def Draw(self, grid, dc, rect, rc):
x = rect.left + (rect.width - self._bmp.GetWidth()) / 2
y = rect.top + (rect.height - self._bmp.GetHeight()) / 2
dc.DrawBitmap(self._bmp, x, y, True)
To use this renderer, you will have to do something like this:
g = MyGrid(self, size=(100,100))
g.SetColLabelRenderer(0, MyCornerLabelRenderer())
This will put the image into the first column.

Python3: How to dynamically resize button text in tkinter/ttk?

I want to know how to arrange for the text on a ttk widget (a label or button, say) to resize automatically.
Changing the size of the text is easy, it is just a matter of changing the font in the style. However, hooking it into changes in the size of the window is a little more tricky. Looking on the web I found some hints, but there was nowhere a complete answer was posted.
So, here below is a complete working example posted as an answer to my own question. I hope someone finds it useful. If anyone has further improvements to suggest, I will be delighted to see them!
The example below shows two techniques, one activated by re-sizing the window (see the resize() method, bound to the <Configure> event), and the other by directly changing the size of the font (see the mutate() method).
Other code necessary to get resizing working is the grid configuration code in the __init__() method.
When running the example, there is some interaction between the two methods, but I think in a 'real' situation one technique would be sufficient, so that issue won't arise.
from tkinter import *
from tkinter.ttk import *
class ButtonApp(Frame):
"""Container for the buttons."""
def __init__(self, master=None):
"""Initialize the frame and its children."""
super().__init__(master)
self.createWidgets()
# configure the frame's resize behaviour
master.columnconfigure(0, weight=1)
master.rowconfigure(0, weight=1)
self.grid(sticky=(N,S,E,W))
# configure resize behaviour for the frame's children
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# bind to window resize events
self.bind('<Configure>', self.resize)
def createWidgets(self):
"""Make the widgets."""
# this button mutates
self.mutantButton = Button(self, text='Press Me',
style='10.TButton')
self.mutantButton.grid(column=0, row=0, sticky=(N,S,E,W))
self.mutantButton['command'] = self.mutate
# an ordinary quit button for comparison
self.quitButton = Button(self, text='Quit', style='TButton')
self.quitButton.grid(column=0, row=1, sticky=(N,S,E,W))
self.quitButton['command'] = self.quit
def mutate(self):
"""Rotate through the styles by hitting the button."""
style = int(self.mutantButton['style'].split('.')[0])
newStyle = style + 5
if newStyle > 50: newStyle = 10
print('Choosing font '+str(newStyle))
self.mutantButton['style'] = fontStyle[newStyle]
# resize the frame
# get the current geometries
currentGeometry = self._root().geometry()
w, h, x, y = self.parseGeometry(currentGeometry)
reqWidth = self.mutantButton.winfo_reqwidth()
reqHeight = self.mutantButton.winfo_reqheight()
# note assume height of quit button is constant at 20.
w = max([w, reqWidth])
h = 20 + reqHeight
self._root().geometry('%dx%d+%d+%d' % (w, h, x, y))
def parseGeometry(self, geometry):
"""Geometry parser.
Returns the geometry as a (w, h, x, y) tuple."""
# get w
xsplit = geometry.split('x')
w = int(xsplit[0])
rest = xsplit[1]
# get h, x, y
plussplit = rest.split('+')
h = int(plussplit[0])
x = int(plussplit[1])
y = int(plussplit[2])
return w, h, x, y
def resize(self, event):
"""Method bound to the <Configure> event for resizing."""
# get geometry info from the root window.
wm, hm = self._root().winfo_width(), self._root().winfo_height()
# choose a font height to match
# note subtract 30 for the button we are NOT scaling.
# note we assume optimal font height is 1/2 widget height.
fontHeight = (hm - 20) // 2
print('Resizing to font '+str(fontHeight))
# calculate the best font to use (use int rounding)
bestStyle = fontStyle[10] # use min size as the fallback
if fontHeight < 10: pass # the min size
elif fontHeight >= 50: # the max size
bestStyle = fontStyle[50]
else: # everything in between
bestFitFont = (fontHeight // 5) * 5
bestStyle = fontStyle[bestFitFont]
# set the style on the button
self.mutantButton['style'] = bestStyle
root = Tk()
root.title('Alice in Pythonland')
# make a dictionary of sized font styles in the range of interest.
fontStyle = {}
for font in range(10, 51, 5):
styleName = str(font)+'.TButton'
fontName = ' '.join(['helvetica', str(font), 'bold'])
fontStyle[font] = styleName
Style().configure(styleName, font=fontName)
# run the app
app = ButtonApp(master=root)
app.mainloop()
root.destroy()

Resources